>SETUP(您可以稍后阅读并首先跳到场景部分(
这是一个旧应用程序,手动设置CoreData堆栈如下:
+ (NSManagedObjectContext *)masterManagedObjectContext
{
if (_masterManagedObjectContext) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self createPersistentStoreCoordinator];
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_masterManagedObjectContext.retainsRegisteredObjects = YES;
_masterManagedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_masterManagedObjectContext.persistentStoreCoordinator = coordinator;
}
return _masterManagedObjectContext;
}
+ (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext) {
return _managedObjectContext;
}
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.retainsRegisteredObjects = YES;
_managedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_managedObjectContext.parentContext = masterContext;
}
return _managedObjectContext;
}
+ (NSManagedObjectContext *)newManagedObjectContext
{
__block NSManagedObjectContext *newContext = nil;
NSManagedObjectContext *parentContext = [self managedObjectContext];
if (parentContext) {
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
newContext.parentContext = parentContext;
}
return newContext;
}
然后递归保存上下文:
+ (void)saveContext:(NSManagedObjectContext *)context
{
[context performBlockAndWait:^{
if (context.hasChanges && context.persistentStoreCoordinator.persistentStores.count) {
NSError *error = nil;
if ([context save:&error]) {
NSLog(@"saved context: %@", context);
// Recursive save parent context.
if (context.parentContext) [self saveContext:context.parentContext];
}
else {
// do some real error handling
NSLog(@"Could not save master context due to %@", error);
}
}
}];
}
场景
该应用程序从服务器加载大量数据,然后首先在newContext
内部执行更新,然后合并到mainContext
->masterContext
->persistentStore
中。
由于数据很多,同步过程已分为大约 10 个异步线程 =>我们一次有 10 个newContext
。
现在,数据很复杂,比如parents <-> children (same class)
.1parent
可以有很多children
,一个child
可以有一个mother, father, god father, step mother...
,所以它是n-n relationship
。首先,我们获取parent
,然后执行获取child
,然后将child
设置为parent
,依此类推。
服务器有点愚蠢,它无法发送禁用的对象。但是,客户希望从后端控制应用程序对象的显示,因此我有 2 个属性来执行此操作:
hasUpdated
:在加载过程开始时,执行批量更新,将所有对象的hasUpdated
设置为NO。从服务器获取数据后,将此属性更新为 YES。isActive
:完成所有加载后,如果hasUpdate == NO
,则执行将此属性批量更新为 NO 。然后,我有一个过滤器,它不会显示带有isActive == NO
的对象
问题
客户抱怨为什么某些对象丢失,即使它们在后端启用。在遇到这个奇怪的问题后,我已经挣扎和调试了很长时间:
- newContext.updateObjects : { obj1.ID = 100,
hasUpdated == YES
} - "保存的新上下文">
- mainContext.updateObjects: {obj1.ID = 100,
hasUpdated == NO
}
我就到此为止。显然,master 已更新 = 否,最后isActive
将设置为 no,这会导致丢失对象。
如果每次都发生,那么可能更容易修复(也许?但是,它像这样发生:
- 第一次运行(第一次,我的意思是应用程序从调用
appDidFinishLaunch...
的地方开始(:全部正确 - 第二次:失踪(153个对象(
- 第三次:全部正确
- 第四次:失踪(153个对象((再次?正是那些有多个父母的人,我相信是这样!
- 第五次:再次更正
- 。等等。
此外,看起来这种情况发生在具有相同上下文的对象(相同的newContext
(。难以置信。
问题
为什么会这样?我该如何解决这个问题?如果这些物体没有孩子,我的生活会更容易!!!
奖金
如果您想知道批量更新的情况,请参见下文。注意:
- 下载请求位于异步队列中:
_shareInstance.apiQueue = dispatch_queue_create("product_request_queue", DISPATCH_QUEUE_CONCURRENT);
- 解析响应和更新属性在队列中是同步的:
_shareInstance.saveQueue = dispatch_queue_create("product_save_queue", DISPATCH_QUEUE_SERIAL);
- 每当解析完成时,我都会执行保存
newContext
并在同一串行队列中调用updateProductActiveStatus:
。如果所有请求都已完成,则执行批处理更新状态。由于请求是在并发队列中完成的,因此它总是比保存(串行(队列更早完成,因此它几乎是万无一失的过程。
法典:
// Load Manager
- (void)resetProductUpdatedStatus
{
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.propertiesToUpdate = @{ @"hasUpdated" : @(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(@"Batch update hasUpdated: %@", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
[[CoreDataUtil managedObjectContext] performBlockAndWait:^{
[[CoreDataUtil managedObjectContext] refreshAllObjects];
}];
}];
}
- (void)updateProductActiveStatus:(SyncComplete)callback
{
if (self.apiRequestList.count) return;
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.predicate = [NSPredicate predicateWithFormat:@"hasUpdated = NO AND isActive = YES"];
request.propertiesToUpdate = @{ @"isActive" : @(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(@"Batch update isActive: %@", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
NSManagedObjectContext *maincontext = [CoreDataUtil managedObjectContext];
NSLog(@"Refreshed master");
[maincontext performBlockAndWait:^{
[maincontext refreshAllObjects];
NSLog(@"Refreshed main");
// Callback
if (callback) dispatch_async(dispatch_get_main_queue(), ^{ callback(YES, nil); });
}];
}];
}
mergePolicy
是邪恶的。 唯一正确的合并策略是NSErrorMergePolicy
任何其他策略都要求核心数据静默失败,并且在您预期时不更新。
我怀疑您的问题是您在使用后台上下文同时写入核心数据。(我知道你说你有一个串行队列 - 但如果你在队列中调用 performBlock,那么每个块都会同时执行(。当有冲突时,内容会被覆盖。 您应该只以一种同步方式写入核心数据。
我写了一个关于如何使用NSPersistentContainer完成此操作的答案: NSPersistentContainer并发用于保存到核心数据,我建议您将代码迁移到它。 这真的不应该那么难。
如果你想让代码尽可能接近当前,那也不是那么难。
创建串行操作队列:
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;
并使用此队列进行所有写入:
- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
void (^blockCopy)(NSManagedObjectContext*) = [block copy];
[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext* context = [CoreDataUtil newManagedObjectContext];
[context performBlockAndWait:^{
blockCopy(context);
[CoreDataUtil saveContext:context];
}];
}]];
}
也可能是对象已更新,但您看不到它,因为您依赖于 fetchedResultsController 进行更新。 并且 fetchedResultsController 不会从批处理更新请求中更新。