2015-11-06 33 views
2

當我使用Swift進行核心數據提取請求時,我遇到了內存泄漏。但是,我在應用程序的不同部分中創建了幾乎相同的提取請求,但不會導致泄漏。在這兩種情況下,獲取請求都是在視圖控制器的viewDidLoad中進行的,並且獲取請求的結果被分配給視圖控制器的可選屬性。使用Swift核心數據提取請求中的內存泄漏

下面是不會引起任何泄漏的讀取請求的方法:

class LocationFilter { 
    //Lots of other code... 
    class func getAllPlacesOfRegionType<T: Region>(regionType: RegionType) -> [T] { 
     let fetchRequest = NSFetchRequest(entityName: regionType.rawValue) 

     var places: [T] 
     do { 
      places = try CoreDataStack.sharedInstance.context.executeFetchRequest(
       fetchRequest) as! [T] 
     } catch let error as NSError { 
      NSLog("Fetch request failed: %@", error.localizedDescription) 
      places = [T]() 
     } 

     places.sortInPlace({ (firstPlace, nextPlace) -> Bool in 
      //Ingenious sorting code... 
     }) 

     return places 
    } 
} 

這種方法被稱爲一個的viewController的viewDidLoad中,結果被分配給屬性var allRegions: [Region]?沒有任何泄漏。下面的代碼:

class PlacesTableViewController: UITableViewController { 
    var allRegions: [Region]? 
    @IBOutlet weak var segmentedRegions: UISegmentedControl! 

    @IBAction func selectRegionSegment(sender: UISegmentedControl) { 
     // When the segmented control is tapped, the appropriate list will be loaded. 
     switch sender.selectedSegmentIndex { 
     case 0: //Country Segment 
      allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Country) 
     case 1: //States segment 
      allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Province) 
     case 2: //Cities segment 
      allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.City) 
     case 3: //Addresses segment 
      allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Address) 
     default: 
      break 
     } 

     // Then reload the cells with animations. 
     let index = NSIndexSet(index: 0) 
     tableView.reloadSections(index, withRowAnimation: UITableViewRowAnimation.Automatic) 
    } 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     selectRegionSegment(segmentedRegions) 
    } 
} 

下面的方法被調用viewDidLoad中不同的viewController的設置屬性var allDays: [Day]!

class DateFilter { 
    //Lots of other code in the class... 
    class func getAllDays() -> [Day] { 
     let fetchRequest = NSFetchRequest(entityName: "Day") 

     let days: [Day] 
     do { 
      days = try CoreDataStack.sharedInstance.context.executeFetchRequest(
       fetchRequest) as! [Day] 
     } catch let error as NSError { 
      NSLog("Fetch request failed: %@", error.localizedDescription) 
      days = [Day]() 
     } 

     return days 
    } 
} 

在這裏它被稱爲:

class SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 
    var allDays: [Day]! 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     allDays = DateFilter.getAllDays() 

     let backgroundView = UIView(frame: CGRectZero) 
     tableView.tableFooterView = backgroundView 
     tableView.backgroundColor = UIColor.groupTableViewBackgroundColor() 
    } 
} 

Xcode的儀器檢測內存泄漏時,這是調用。據推測負責的圖書館是libswiftFoundation.dylib,負責的圖書館是static Array<A>._forceBridgeFromObjectiveC<A>(NSArray, result:inout [A]?) ->()。當我查看Cycles & Root時,它在根處顯示NSArray+16 list: (null)+24 [no ivar]: (null)分叉。

我做錯了我如何存儲我的提取請求的結果?或者這是Swift如何與Core Data交互的一個錯誤?

編輯:按照Mundi的建議整理代碼。 編輯2:添加了調用提取請求函數的代碼。

回答

1

嘗試很多事情之後,我敢肯定,它在獲取我的Day實體時,核心數據如何轉換NSArray的斯威夫特陣列的錯誤。也許它與Day實體的關係或屬性有關。我會繼續研究它。

現在,我找到了解決辦法。儀器一直指向libswiftFoundation方法,用於將NSArray轉換爲Array,並且Cycles & Roots不斷顯示帶有no ivar的NSArray。基於我的研究,這與由獲取請求創建的NSArray的初始化有關,後者在幕後轉換爲Swift數組。由於我無法改變這一點,我從提取結果中創建了一個新的Array:

override func viewDidLoad() { 
    super.viewDidLoad() 

    let fetchResults: [Day] = DateFilter.getAllDays() 
    allDays = fetchResults.map({$0}) 

    let backgroundView = UIView(frame: CGRectZero) 
    tableView.tableFooterView = backgroundView 
    tableView.backgroundColor = UIColor.groupTableViewBackgroundColor() 
} 

神奇的是,內存泄漏消失了!我不完全確定爲什麼fetchResults陣列有漏洞,但這似乎是問題的根源。

+1

謝謝,我有完全相同的問題。你有沒有找出確切的原因? (泄漏對我來說只發生在iPhone 5s設備上,iPhone 6s和模擬iPhone 5s沒有泄漏。) – dvlpr

+0

不客氣!不幸的是,我仍然不知道確切的原因。正如Mac Bellingrath所建議的那樣,我不能排除名稱空間衝突,但更改屬性的名稱並沒有爲我解決它。我開始認爲這是Apple框架中的一些東西;我懷疑這是由CoreData將返回的'NSArray'轉換爲Swift數組的方式引起的。 – ConfusedByCode

+0

@dvlpr與iPhone 5s(9.3.1)設備上的OP具有相同的泄漏,我在iPhone 4(9.2.1)上沒有任何泄漏。順便說一下,我的實體與ConfusedByCode沒有任何關係。 –

0

我認爲你的提取代碼過於冗長。特別是,我認爲將獲取結果分配給另一個變量會導致Objective-C類NSArray(這是獲取請求的結果類型)進行某種轉換,這會以某種方式導致泄漏。 (我也不完全理解爲什麼,但我認爲它也與這是一個定義變量的類函數有關)。

我建議簡化代碼。

let request = NSFetchRequest(entityName: "Day") 
do { return try context.executeFetchRequest(request) as! [Day]) } 
catch { return [Day]() } 
+0

感謝您的提示。我包括瞭解開結果的額外步驟,以便儘可能地類似於沒有問題的結果,但我同意,這不是必需的。我會試試這個。 – ConfusedByCode

+0

我嘗試了你的方式(在do-catch塊中有返回語句)以及我在編輯中做到的方式。仍然得到相同的內存泄漏。 – ConfusedByCode

+0

然後泄漏是由調用此方法的代碼引起的。您可以編輯您的問題並添加該代碼。 – Mundi

1

剛剛經歷了這個完全相同的問題。我在每個捕獲列表中都加入了弱自我,無濟於事。原來是一個名稱空間衝突,系統無法推斷出我正在談論的數組(或者其類型)(因爲它們共享相同的名稱)。我從'categories'中重命名了下面的'foundCategories',這是我的視圖控制器的屬性。

func fetchCategoriesAndNotes() { 

categories = [] 

    fetchEntity("Category", predicates: nil) { [weak self] (found) -> Void in 

     guard let foundCategories = found as? [Category] else { return } 

     for category in foundCategories {} ... } } 

內存泄漏消失了。

+0

有趣。我要檢查一下,因爲我目前的解決方法增加了一個不必要的步驟。你是怎麼弄出來的? – ConfusedByCode

+0

我終於嘗試重命名屬性,並且它對內存泄漏沒有影響。我認爲在我的情況下,它可能與我爲幾個VC使用相同的NSManagedObjectContext的方式有關,但現在,我只是接受我的解決方案作爲答案。感謝您的建議。 – ConfusedByCode