使用CoreData在两个队列之间传递对象引用的最佳做法是什么?

时间:2017-06-25 09:20:37

标签: ios objective-c multithreading core-data

我正面临着应用程序架构设计的决定。

应用程序使用CoreData来保存用户信息,相同的信息也存储在REST接口可访问的远程服务器上。当应用程序启动时,我提供要显示的CoreData的缓存信息,同时从服务器获取更新。获取的信息也会自动保留。

所有这些任务都在后台队列中执行,以便不阻止主线程。我对我的persistenContainer和名为User的NSManagedObject保持强烈的引用。

@property (nonatomic, retain, readwrite) User *fetchedLoggedInUser;

正如我所说,填充了用户通过

执行获取请求
[_coreDataManager.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context) {
        (...)
        NSArray <User*>*fetchedUsers = [context executeFetchRequest:fetchLocalUserRequest error:&fetchError];
        (...)
        self.fetchedLoggedInUser = fetchedUsers.firstObject;
        //load updates from server
        Api.update(){
            //update the self.fetchedLoggedInUser properties with data from the server
            (...)
            //persist the updated data
            if (context.hasChanges) {

            context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
            NSError *saveError = nil;

            BOOL saveSucceeded = [context save:&saveError];

            if (saveSucceeded) {

                //notify the App about the updates
                //**here is the problem**
            }
        };
}];

所以显而易见的是,在执行backgroundTask之后,我的self.fetchedLoggedInUser不再存在于内存中,因为它对我的PersistentContainer的performBackgroundTask()提供的NSManagedObjectContext的弱引用。 因此,如果我尝试从另一个模型访问信息,则值为nil。 每次我想要访问其值时,将获取的ManagedObject保留在内存中并且不必再次获取它的最佳做法是什么?

A)Documentation中,Apple建议使用ManagedObject的objectID来在队列之间传递对象 传递队列之间的引用

  

NSManagedObject实例不打算在它们之间传递   队列。这样做可能会导致数据损坏和终止   申请。当需要交出托管对象时   从一个队列引用另一个队列,必须完成   NSManagedObjectID实例。

     

通过调用来检索托管对象的托管对象ID   NSManagedObject实例上的objectID方法。

该情况的完美工作代码将是用此代码替换if(saveSucceeded)检查:

if (saveSucceeded) {

   dispatch_async(dispatch_get_main_queue(), ^{
      NSError *error = nil;
      self.fetchedLoggedInUser = [self.coreDataManager.persistentContainer.viewContext existingObjectWithID:_fetchedLoggedInUser.objectID error:&error];
      (...)
      //notify the App about the updates
   });
}

但我认为这可能不是最好的解决方案,因为这需要访问mainQueue上的mainContext(在本例中为persistentContainer的viewContext)。这可能与我在这里要做的事情相矛盾(在后台执行,以达到最佳性能)。

我的其他选择(好吧,这些,我想出来的)将是

B)将用户信息存储在Singleton中,并在每次从CoreData获取信息并将其保存到CoreData时更新它。在这种情况下,我不需要担心保持NSManagedObject上下文的活动。我可以对我的persistentContainer performBackgroundTask提供的私有后台上下文执行任何更新,每当我需要保留新的/编辑的用户信息时,我可以从数据库中重新获取NSManagedObject,设置属性,保存我的上下文然后更新Singleton。我不知道这是否优雅。

C)编辑我的self.fetchedLoggedInUser的getter方法以包含获取请求并获取所需信息(由于访问数据库时的开销,这可能是最糟糕的)我甚至不确定这是否会起作用。

我希望其中一个解决方案实际上是最佳做法,但我希望听到您的建议为什么/如何或为何/如何/如何不处理信息的传递。

TL:DR; 在加载和存储新信息时,最好的做法是在整个应用中保持用户信息的可用性主要来自backgroundQueues?

PS:我不希望每次需要在我的一个ViewControllers中访问它时获取信息,我想将数据存储在中心结上,以便可以轻松地从每个ViewController访问它。目前,self.fetchedLoggedInUser是整个应用程序中使用的单例的属性。我发现这可以节省大量冗余代码,使用Singleton可以使信息的加载和存储更加清晰,并减少对数据库的访问次数。如果这被认为是不好的做法,我很乐意和你讨论这个问题。

2 个答案:

答案 0 :(得分:2)

使用NSFetchedResultsController - 它们非常有效,您甚至可以将它们用于一个对象。 FetchedResultsController执行一次提取,然后监视核心数据以进行更改。当它改变时,你有一个它已经改变的回调。它也适用于任何核心数据设置。只要更改传播到主上下文(使用newBackgroundContextperformBackgroundTask或子上下文或其他),fetchedResultsController就会更新。因此,您可以自由更改核心数据堆栈,而无需更改监控代码。

一般来说,我不喜欢保留指向ManagedObjects的指针。如果从数据库中删除该条目,则当您尝试访问它时,managedObject将崩溃。 fetchedResultsController始终可以安全地读取fetchedObjects,因为它会跟踪您的删除。

显然将NSFetchedResultsController附加到viewContext并且只从主线程中读取它。

答案 1 :(得分:1)

在我看来,我提出了一个非常优雅的解决方案。 从一开始我使用名为sharedCoreDataManager的Singleton,我添加了一个初始化的属性backgroundContext

self.backgroundContext = _persistentContainer.newBackgroundContext;
_backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_backgroundContext.retainsRegisteredObjects = YES;

并由sharedCoreDataManager保留。我正在使用此上下文来执行任何任务。通过调用_backgroundContext.retainsRegisteredObjects我的NSManagedObject由backgroundContext保留,sharedCoreDataManager本身(就像我说的)由我的Singleton save()保留。

我认为这是一个优雅的解决方案,因为我可以随时从后台访问ManagedObject线程安全。我也不需要任何额外的课程来保存用户信息。最重要的是,我可以随时轻松修改用户信息,然后根据需要在backgroundContext上致电{{1}}。

也许我将来将它作为一个孩子添加到我的viewContext中,我会评估性能并最终更新这个答案。

欢迎您提出更好的解决方案或讨论此主题。