我做了一些研究,并在Objective-C代码上找到了一些不错的信息,但对于Swift来说几乎没有。我认为这是一个非常常见的模式,所以希望我们能够确定如何正确地做到这一点。我已经取得了一些非常重要的进步,感觉我已经非常接近了,但我只是在Swift的深度。
目标:制作一个使用后台线程解析数据并执行长获取请求的应用,并拥有一个使用NSFetchedResults控制器的主线程。
我的一个函数中的代码来分拆一个新的线程
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&#34 ;.突出显示触发错误的行是
managedObjectContext?.save(nil)
它旁边的错误是" EXC_BAD_ACCESS(代码1,地址= ...
如果我只是等待后台线程完成,完成后,我也会收到一个错误,这次跟踪到" didChangeObject" NSFetchedResultsController的方法。它说"在展开可选值时意外地发现了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 Object方法会触发相应的方法来执行这些动画,在这里我得到了nil错误。显然,重点在于,当获取背景数据时,它将平滑地进行动画制作,但不会这样做,而是会爆炸。如果我评论这个功能,我不会得到任何错误,但也放松了我希望的平滑动画。附带的didChangeObject方法如下。它主要直接来自NSFetchedResultController的快速文档:
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)
}
}
答案 0 :(得分:9)
最后,我最终研究了许多不同的线程方法。最有用的是多个上下文,其中包含了here所述的通知中心。我还实现了多个上下文解决方案,但最终还是降级到了另一个。我的问题结果是我在多个NSFetchedResultsControllers之间共享一个委托,而没有检查传入的控制器是否与表当前使用的控制器相同。每当数据自动重新加载时,这都会产生超出界限的错误。
我的后台线程解决方案很简单。
使用performBlock
调用背景上下文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)
}
}
将更改合并到mainContext中。
我的初始设置实际上非常接近正确,我不知道的是,当你设置backgroundContext时,你可以给它一个并发类型
let childContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
然后,只要你想做后台线程的东西,就可以使用context.performBlock(如上所示)在后台线程中调用上下文。
此外,NSFetchedResultsController使用mainContext作为其上下文,以便在解析期间不会阻止它们。
<强>更新强>
有多种方法可以执行后台线程,上面的解决方案只有一种。 Quellish描述了另一种流行的方法in his article here。它非常有用,我推荐它,它描述了队列限制的嵌套上下文方法。
答案 1 :(得分:2)
在调用self.tableView.cellForRowAtIndexPath
之前,您应该检查self.configureCell...
是否为零 - 如果相关行不再可见,则可以为零。
FRC委托方法应该对tableView进行必要的更改,但鉴于您可能有很多来自后台的更新,您可以在reloadData
方法中添加controllerDidChangeContent:
。