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)
这样的东西. 1 parent
可以有很多children
, 和一个 child
可以有一个 mother, father, god father, step mother...
, 所以它是 n-n relationship
.首先,我们获取 parent
, 然后执行 fetch child
然后设置 child
至 parent
, 等等。
服务器有点笨,它不能发送禁用的对象。但是,客户希望从后端控制应用程序对象的显示,所以我有 2 个属性可以做到这一点:
-
hasUpdated
: 在加载过程开始时,执行批量更新,设置所有对象的hasUpdated
否。从服务器获取数据后,将此属性更新为 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 个对象)
- 第三次:全部正确
- 第 4 次:丢失(153 个对象)(再次?正是那些有多个 parent 的对象,我相信是这样!)
- 第5次:再次纠正
- ...等等。
此外,对于具有相同上下文(相同的 newContext
)的对象来说,这似乎发生了。难以置信。
问题
为什么会这样?我该如何解决?如果那些对象没有 child ,我的生活会更轻松!!!!
奖金
如果你想知道批量更新是如何进行的,请看下面。注意:
- 下载请求在异步队列中:
_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
是邪恶的。唯一正确的 mergePolicy 是 NSErrorMergePolicy
任何其他策略都是要求核心数据静默失败,而不是在您期望的时候更新。
我怀疑您的问题是您正在使用背景上下文同时写入核心数据。 (我知道你说你有一个串行队列 - 但如果你在队列中调用 performBlock 那么每个 block 都会同时执行)。当有冲突时,东西会被覆盖。您应该只以一种同步方式写入核心数据。
我写了一篇关于如何使用 NSPersistentContainer 完成此操作的答案: NSPersistentContainer concurrency for saving to core data我建议您将代码迁移到它。这真的不应该那么难。
如果您想让代码尽可能接近当前的代码,那也不难。
制作串行操作队列:
_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 不会根据批量更新请求进行更新。
关于ios - privateQueue managedObjectContext 的奇怪问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45467739/