我正面临着应用程序架构设计的决定。
应用程序使用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可以使信息的加载和存储更加清晰,并减少对数据库的访问次数。如果这被认为是不好的做法,我很乐意和你讨论这个问题。
答案 0 :(得分:2)
使用NSFetchedResultsController
- 它们非常有效,您甚至可以将它们用于一个对象。 FetchedResultsController执行一次提取,然后监视核心数据以进行更改。当它改变时,你有一个它已经改变的回调。它也适用于任何核心数据设置。只要更改传播到主上下文(使用newBackgroundContext
或performBackgroundTask
或子上下文或其他),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中,我会评估性能并最终更新这个答案。
欢迎您提出更好的解决方案或讨论此主题。