【发布时间】:2017-08-02 17:48:07
【问题描述】:
设置(您可以稍后阅读并先跳到场景部分)
这是一个旧应用程序,手动设置 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)。 1 个parent 可以有多个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.updatedObjects : { obj1.ID = 100,
hasUpdated == YES} - “保存的新上下文”
- mainContext.updatedObjects: {obj1.ID = 100,
hasUpdated == NO}
// 我会停在这里。显然,master 得到了更新 = NO,最后isActive 将设置为 no,这会导致对象丢失。
如果它每次都发生,那么可能更容易修复(¿也许?)。但是,它是这样发生的:
- 第一次运行(第一次,我的意思是应用程序从调用
appDidFinishLaunch...的位置开始):都正确 - 第二次:丢失(153 个对象)
- 第三次:全部正确
- 第四次:失踪(153 个对象)(再次?正是那些有多个父母的人,我相信是这样!)
- 第 5 次:再次更正
- ... 以此类推。
此外,这似乎发生在具有相同上下文(相同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); });
}];
}];
}
【问题讨论】:
标签: ios objective-c core-data concurrency batch-updates