Ios 具有核心数据和NSFetchedResultsController的后台线程
我做了一些研究,发现了一些关于objective-C代码的不错的信息,但对Swift几乎没有。我认为这是一个非常常见的模式,所以希望我们能找到正确的方法。我已经取得了一些相当重要的进步,感觉自己已经很接近了,但我对Swift的了解还不够深入 目标:制作一个应用程序,使用后台线程解析数据并执行长取数请求,并且有一个主线程使用NSFetchedResults控制器 从我的一个函数中编写代码来派生一个新线程Ios 具有核心数据和NSFetchedResultsController的后台线程,ios,multithreading,core-data,swift,nsfetchedresultscontroller,Ios,Multithreading,Core Data,Swift,Nsfetchedresultscontroller,我做了一些研究,发现了一些关于objective-C代码的不错的信息,但对Swift几乎没有。我认为这是一个非常常见的模式,所以希望我们能找到正确的方法。我已经取得了一些相当重要的进步,感觉自己已经很接近了,但我对Swift的了解还不够深入 目标:制作一个应用程序,使用后台线程解析数据并执行长取数请求,并且有一个主线程使用NSFetchedResults控制器 从我的一个函数中编写代码来派生一个新线程 let tQueue = NSOperationQueue() let testThread1
let tQueue = NSOperationQueue()
let testThread1 = testThread()
tQueue.addOperation(testThread1)
testThread1.threadPriority = 0
testThread1.completionBlock = {() -> () in
println("Thread Completed")
}
我上的课是用来做线的
class testThread: NSOperation{
var delegate = UIApplication.sharedApplication().delegate as AppDelegate
var threadContext:NSManagedObjectContext?
init(){
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: nil)
}
override func main(){
self.threadContext = NSManagedObjectContext()
threadContext!.persistentStoreCoordinator = delegate.persistentStoreCoordinator
...
//Code that actually does a fetch, or JSON parsing
...
threadContext!.save(nil)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func contextDidSave(notification: NSNotification){
let sender = notification.object as NSManagedObjectContext
if sender !== self.threadContext{
self.threadContext!.mergeChangesFromContextDidSaveNotification(notification)
}
}
}
我不会包含NSFetchedResultsController的所有代码,但我有一个链接到主上下文的代码。当我的线程被注释掉时,应用程序运行正常,它将阻塞UI并解析/获取需要插入核心数据的数据,当全部完成时,UI将解锁
当我添加线程时,只要我在UI中执行任何可能触发保存到主上下文的操作(在本例中,tappedOnSection表函数执行保存),应用程序就会崩溃,控制台中唯一显示的就是。“lldb”。触发错误的高亮显示行为
managedObjectContext?.save(nil)
它旁边的错误是“EXC\u BAD\u访问(代码1,地址=
如果我只是等待后台线程完成,那么在完成时,我也会在这次跟踪NSFetchedResultsController的“didChangeObject”方法时收到一个错误。它说“在展开可选值时意外发现了nil,并标记以下情况:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch(type){
... other cases
case NSFetchedResultsChangeType.Update:
self.configureCell(self.tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
...other cases
}
}
我假设我在并发性方面遇到了一些问题,但我没有正确处理。我认为监视更改的NSNotification可以处理这些问题,但我肯定遗漏了其他一些东西
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: nil)
...
//Code here calls the function that starts the thread shown previously to do a background fetch
}
func contextDidSave(notification: NSNotification){
let sender = notification.object as NSManagedObjectContext
if sender !== self.managedObjectContext!{
println("Save Detected Outside Thread Main")
self.managedObjectContext!.mergeChangesFromContextDidSaveNotification(notification)
}
}
更新:
在你们的帮助下,我已经能够定位错误。问题似乎出在NSFetchedResultsController中的didChangeObject方法。如果数据发生更改,或者插入新行,didChange对象方法会触发相应的方法来执行这些动画,在这里我得到了零错误。显然关键是,当获取背景数据时,它将只是平滑地设置动画,但不是这样做,它会爆炸。如果我注释掉这个函数,我不会得到任何错误,但也会失去我希望的平滑动画。下面是附带的didChangeObject方法。它大部分直接来自NSFe上的swift文档tchedResultController:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch(type){
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Update:
if self.tableView.cellForRowAtIndexPath(indexPath!) != nil{
self.configureCell(self.tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
}
case NSFetchedResultsChangeType.Move:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
}
}
在调用
self.configureCell…
之前,应检查self.tableView.cellforrowatinexpath
是否为nil-如果相关行不再可见,则可以为nil
FRC委托方法应该对tableView进行必要的更改,但是考虑到您可能有许多来自后台的更新,您可以在
controllerdChangeContent:
方法中添加reloadData
。最后我研究了许多不同的线程处理方法。最有用的结果证明是多个上下文,使用如上所述的通知中心,我还实现了多个上下文解决方案,但最终降级为另一个。我的问题是,我在多个NSFetchedResultsController之间共享一个委托,而没有检查传入的控制器是否与第1个相同表中的当前正在使用。每当数据自动重新加载时,就会产生越界错误
我的后台线程解决方案很简单
context.performBlock {
//background code here
NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: nil)
func contextDidSave(notification: NSNotification) {
let sender = notification.object as NSManagedObjectContext
if sender != managedObjectContext {
managedObjectContext!.mergeChangesFromContextDidSaveNotification(notification)
}
}
let childContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
然后,您可以随时使用context.performBlock(如上所示)在后台线程中调用上下文
此外,NSFetchedResultsController使用mainContext作为其上下文,这样在解析过程中它们就不会被阻塞
更新
有多种方法可以执行后台线程,上面的解决方案只是其中之一。Quellish描述了另一种流行的方法。它提供了非常丰富的信息,我推荐它,它描述了队列限制的嵌套上下文方法。在调用
s之前,您应该检查self.tableView.cellforrowatinexpath
是否为nilelf.configureCell…
-如果相关行不再可见,则该值可以为零。这似乎有助于修复我遇到的一些错误,当主上下文和后台上下文同时发生保存时,我仍然遇到并发错误,但它没有使应用程序崩溃,只是使UI混乱了大约15秒,并且然后它神奇地修复了自己。我需要在我的contextDidSave方法中执行tableView.reloadData吗?FRC委托方法应该对tableView进行必要的更改,但是考虑到您可能有许多来自后台的更新,您可以将reloadData放在controllerDidChangeContent:
方法中。不需要和你的车祸有关,但是…“但我肯定错过了别的东西。”是的。您正在使用队列限制。不要将合并通知与队列限制一起用于在上下文之间通信更改,请改用上下文嵌套。合并通知适用于线程限制,但由于许多原因,无法正确使用队列限制。另请参阅:我想您仍然存在一些无法解决的问题ms indidChangeObject
:在.Update
案例中,测试cellforrowatinexpath(indexPath!)!=nil
,但通过