NSFetchedResultsController在后台线程上使用核心数据

时间:2015-07-21 11:32:54

标签: ios multithreading swift uitableview core-data

我知道Stack Overflow上已经存在很多与此相关的问题。我尝试了各种各样的方法,现在我的情况甚至不确定什么是正确的,而不是什么:S

我有一个相当标准的问题,我希望在一个单独的线程上更新Core Data,并在更新/添加记录时更新UI(tableview)。

根据我的理解,我需要有两个NSManagedObjectContext实例

所以我已经设置了我的AppDelegate方法:

 lazy var managedObjectContext: NSManagedObjectContext? = {
        // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
        let coordinator = self.persistentStoreCoordinator
        if coordinator == nil {
            return nil
        }
        var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = coordinator

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: nil)
        return managedObjectContext
        }()

    // MARK: - Core Data Saving support
    func contextDidSave(notification: NSNotification){
        let sender = notification.object as! NSManagedObjectContext
        if sender !== self.managedObjectContext{
            self.managedObjectContext!.mergeChangesFromContextDidSaveNotification(notification)
        }
    }

现在,应用程序在到达此行时会崩溃

 self.managedObjectContext!.mergeChangesFromContextDidSaveNotification(notification)

我的视图控制器设置如下:

    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

    lazy var fetchedResultsController: NSFetchedResultsController = {
        let workFetchRequest = NSFetchRequest(entityName: "Work")
        let primarySortDescriptor = NSSortDescriptor(key: "createdDate", ascending: true)
        let secondarySortDescriptor = NSSortDescriptor(key: "town", ascending: true)
        workFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]

        let frc = NSFetchedResultsController(
            fetchRequest: workFetchRequest,
            managedObjectContext: self.managedObjectContext!,
            sectionNameKeyPath: "createdDate",
            cacheName: nil)

        frc.delegate = self

        return frc
        }()


    // MARK: NSFetchedResultsControllerDelegate methods
     func controllerWillChangeContent(controller: NSFetchedResultsController) {
        self.tblJobs.beginUpdates()
    }

    func controller(
        controller: NSFetchedResultsController,
        didChangeObject anObject: AnyObject,
        atIndexPath indexPath: NSIndexPath?,
        forChangeType type: NSFetchedResultsChangeType,
        newIndexPath: NSIndexPath?) {

            switch type {
            case NSFetchedResultsChangeType.Insert:

                if let insertIndexPath = newIndexPath {
                    self.tblJobs.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
                }
            case NSFetchedResultsChangeType.Delete:

                if let deleteIndexPath = indexPath {
                    self.tblJobs.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
                }
            case NSFetchedResultsChangeType.Update:

                if let updateIndexPath = indexPath {
                    let cell =  self.tblJobs.dequeueReusableCellWithIdentifier(
                        "JobCell", forIndexPath: updateIndexPath)
                        as! JobTableViewCell

                    let workItem = self.fetchedResultsController.objectAtIndexPath(updateIndexPath) as? Work

                    cell.lblAddress1?.text = workItem!.propertyName + " "  + workItem!.propertyNumber + " " + workItem!.street
                    cell.lblAddress2?.text = workItem!.town + " " + workItem!.locality + " " + workItem!.postcode
                }
            case NSFetchedResultsChangeType.Move:

                if let deleteIndexPath = indexPath {
                    self.tblJobs.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
                }


                if let insertIndexPath = newIndexPath {
                    self.tblJobs.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
                }
            }
    }

     func controller(
        controller: NSFetchedResultsController,
        didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
        atIndex sectionIndex: Int,
        forChangeType type: NSFetchedResultsChangeType) {

            switch type {
            case .Insert:
                let sectionIndexSet = NSIndexSet(index: sectionIndex)
                self.tblJobs.insertSections(sectionIndexSet, withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete:
                let sectionIndexSet = NSIndexSet(index: sectionIndex)
                self.tblJobs.deleteSections(sectionIndexSet, withRowAnimation: UITableViewRowAnimation.Fade)
            default:
                ""
            }
    }

同样,我不确定我在这里调用managedObjectContext的方式是否正确。

最后我的网络课,我意识到很多这个需要抽象出来,但我只是希望这个东西先工作。请注意,我使用dispatch_async来使用一个单独的线程,所有与使用多线程相关的内容都指向使用它,但现在我不确定在使用NSManagedObjectContext的上下文中是否需要它

let lockQueue = dispatch_queue_create("com.app.LockQueue", nil)
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

func makeRequest(url : String, params : [String : String]?, completionHandler: (responseObject: JSON?, error: NSError?) -> ())  -> Request? {

    return Alamofire.request(.GET, url, parameters: params, encoding: .URL)
        .responseString { request, response, responseBody, error in completionHandler(
            responseObject:
            {
                // JSON to return
                var json : JSON?

                if let response = responseBody {
                    // Parse the response to NSData
                    if let data = (response as NSString).dataUsingEncoding(NSUTF8StringEncoding) {
                        json = JSON(data: data)
                    }
                }

                return json

                }(), error: error)
    }
}

func fetchItems(completion: (NSError?) -> Void) {
    self.makeRequest("http://localhost/jobs.json", params: nil) { json, error  in

        var tempContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
        tempContext.parentContext = self.managedObjectContext


            tempContext.performBlock({

                dispatch_async(self.lockQueue) {


                    let entity = "Work"

                    if let obj = json {

                        for (index, object) in obj {

                                var request = NSFetchRequest(entityName: entity)
                                request.predicate = NSPredicate(format: "id = %@", object["Id"].stringValue)
                                var error: NSError?
                                if let entities = tempContext.executeFetchRequest(
                                    request,
                                    error: &error
                                    ) as? [NSManagedObject] {
                                        if entities.count != 0{
                                            println("found an existing record...")
                                            for entity in entities {
                                                                                                  /*TODO: Change this to call an external function to update all entities*/
                                                entity.setValue(object["Town"].stringValue, forKey: "Town")

                                            }

                                            tempContext.save(nil)
                                        }else{
                                            println("Creating a new entity")

                                            Work.createInManagedObjectContext(tempContext,
                                                id: object["Id"].stringValue,
                                                flatNumber: object["FlatNumber"].stringValue,
                                                propertyName: object["PropertyName"].stringValue,
                                                propertyNumber: object["PropertyNumber"].stringValue,
                                                street: object["Street"].stringValue,
                                                locality: object["Locality"].stringValue,
                                                town: object["Town"].stringValue,
                                                postcode: object["Postcode"].stringValue,
                                                createdDate: object["CreatedDate"].stringValue

                                            )

                                        }
                                }

                            if !tempContext.save(&error) {
                                NSLog("Unresolved error \(error), \(error!.userInfo)")
                                abort()

                            }

                        }
                    }

                }

                var error: NSError? = nil
                if tempContext.hasChanges && !tempContext.save(&error) {
                    NSLog("error: %@\n UserInfo: %@\n", error!, error!.userInfo!)
                }
                else {
                    println("Error?")
                }

            })

        completion(error)

    }
}

我遇到的问题是应用程序没有保存记录,并且在插入时它会随机丢失(我试图复制这种情况并且我会更新一些更多信息)当我更新tableview时,我可以看到后台操作正在运行,但表格没有更新。

我意识到另一个用户遇到了类似的问题,结果证明他的NSFetchedResultsController存在问题。但我感觉我的背景线程没有正确设置。

2 个答案:

答案 0 :(得分:2)

不要创建自己的队列并发送给它。通过调用tempContext.performBlock,您要求上下文为您自己的后台队列执行操作。因此,当您在该区块内发货时,您将违反合同。

当您将父级设置为主线程上下文时,您也不需要通知和合并,因此更改将自动保存。但是,当您保存了临时上下文时,您需要保存主上下文,因为它会因您刚刚进行的更改而变脏(这实际上是将数据保存到持久存储中,因此保存到磁盘中)。

当触发主要上下文保存时,如果您要匹配每个批量保存操作,则应在performBlockAndWait:内执行此操作。

答案 1 :(得分:0)

我认为你应该在注册上下文时传递一个NSManagedObjectContext保存通知。

var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: managedObjectContext)

编辑: 问题还在于您正在注册上下文,保存并使用您注册的相同上下文合并更改(self.managedObjectContext)