我做了一些研究,并在Objective-C代码上找到了一些不错的信息,但对于Swift几乎没有 . 我认为这是一个非常常见的模式,所以希望我们能够确定如何正确地做到这一点 . 我取得了一些非常重要的进步,感觉我非常接近,但我只是在Swift的深度 .
Goal: 创建一个使用后台线程解析数据并执行长读取请求的应用程序,并使用一个使用NSFetchedResults控制器的主线程 .
我的一个函数中的代码来分拆一个新的Thread
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_BAD_ACCESS(代码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)
}
}
UPDATE:
在你们的帮助下,我已经能够将错误本地化了 . 似乎来自NSFetchedResultsController的didChangeObject方法是个问题 . 如果数据发生了变化,或者插入了新行,则didChange Object方法会触发相应的方法来执行这些动画,在这里我得到了nil错误 . 显然,重点在于,当获取背景数据时,它将平滑地进行动画制作,但不会这样做,而是会爆炸 . 如果我评论这个功能,比我没有得到任何错误,但也放松了我希望的平滑动画 . 附带的didChangeObject方法如下 . 它主要直接来自NSFetchedResultController上的swift文档:
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)
}
}
2 回答
最后,我最终研究了许多不同的线程方法 . 最有用的是多个上下文,如上所述,通知中心here我也实现了多个上下文解决方案,但最终还是降级到了另一个 . 我的问题结果是我在多个NSFetchedResultsControllers之间共享一个委托,而没有检查传入的控制器是否与表当前使用的控制器相同 . 每当数据自动重新加载时,这都会产生超出范围的错误 .
我的后台线程解决方案很简单 .
创建主要上下文
创建背景上下文 .
使用performBlock调用背景上下文
我的初始设置实际上非常接近正确,我不知道的是,当你设置backgroundContext时,你可以给它一个并发类型
然后你可以使用context.performBlock(如上所示)在后台线程中调用上下文,只要你想做后台线程的东西 .
此外,NSFetchedResultsController使用mainContext作为其上下文,以便在解析期间不会阻止它们 .
UPDATE
有多种方法可以执行后台线程,上面的解决方案只有一种 . Quellish描述了另一种流行的方法in his article here . 它非常有用,我推荐它,它描述了队列限制的嵌套上下文方法 .
你应该在调用
self.configureCell...
之前检查self.tableView.cellForRowAtIndexPath
是否为nil - 如果相关的行不再可见,它可以是nil .FRC委托方法应该对tableView进行必要的更改,但鉴于您可能有很多来自后台的更新,您可以在
controllerDidChangeContent:
方法中放置reloadData
.