2015-11-06 92 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