【发布时间】:2014-11-21 17:59:32
【问题描述】:
我做了一些研究,发现了一些关于 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”。触发错误的突出显示的行是
managedObjectContext?.save(nil)
旁边的错误是“EXC_BAD_ACCESS(code 1, address=...
如果我只是等待后台线程完成,完成后,我也会收到错误,这一次跟踪到 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 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)
}
}
【问题讨论】:
-
您应该在调用
self.configureCell...之前检查self.tableView.cellForRowAtIndexPath是否为nil - 如果相关行不再可见,则它可以为nil。 -
这似乎有助于解决我遇到的一些错误,但在主上下文和后台上下文同时进行保存时,我仍然遇到并发错误,但它没有没有让应用程序崩溃,它只是把 UI 弄乱了大约 15 秒,然后它神奇地修复了自己。我需要在 contextDidSave 方法中执行 tableView.reloadData 吗?
-
FRC 委托方法应该对 tableView 进行必要的更改,但考虑到您可能有许多来自后台的更新,您可以在
controllerDidChangeContent:方法中放置一个 reloadData。 -
与你的崩溃无关,但是......“但我一定是错过了其他东西。”是的。您正在使用队列限制。不要使用带有队列限制的合并通知来传达上下文之间的更改,而是使用上下文嵌套。合并通知适用于线程限制,但由于许多原因不能与队列限制一起正常工作。另请参阅:quellish.tumblr.com/post/93190211147/… 和 quellish.tumblr.com/post/97430076027/…
-
我认为您在
didChangeObject中仍然存在一些indexPath 问题:在.Update的情况下,您测试cellForRowAtIndexPath(indexPath!) != nil,但将cellForRowAtIndexPath(newIndexPath!)传递给configureCell。你应该使用indexPath!。同样在 .Move 案例中,您使用indexPath!进行删除和插入:您应该从indexPath!删除并在newIndexPath!插入。此外,在您的numberOfRowsInSection中,您需要访问部分中的特定元素!当前部分的数组:与第 226 行和第 227 行的viewForHeaderInSection完全相同。
标签: ios multithreading core-data swift nsfetchedresultscontroller