我有一个NSOperationQueue,可以将对象导入到我从Web api获取的Core Data中。每个操作都有我的应用程序的主要managedObjectContext的私有子managedObjectContext。每个操作都会获取要导入的对象,并检查该对象是否已存在,在哪种情况下它会更新现有对象。如果该对象不存在,则创建此新对象。然后,私有子上下文的这些更改将传播到主要托管对象上下文。
此设置对我来说效果非常好,但存在重复问题。
当我在两个不同的并发操作中导入相同的对象时,我得到具有完全相同数据的重复对象。 (它们都检查对象是否存在,并且它们看起来不存在)。我将在同一时间导入2个相同对象的原因是我经常处理“新”api调用以及“获取”api调用。由于我的设置同时具有异步性质,因此很难确保我不会尝试导入重复的对象。
所以我的问题是解决这个特定问题的最佳方法是什么?我考虑过限制导入到最大并发操作为1(由于性能,我不喜欢这样)。类似地,我考虑在每次导入操作后要求保存并尝试处理上下文的合并。另外,我之后考虑过修饰数据以偶尔清理重复数据。最后,我考虑过只处理所有获取请求的重复项。但是这些解决方案对我来说都不是很好,也许我已经看过一个简单的解决方案了。
答案 0 :(得分:5)
所以问题是:
对我而言,上下文之间的合并听起来并不合适 - 上下文不是线程安全的。因此,对于合并发生,在其他上下文的线程/队列上不能进行任何其他操作。因此,您无法消除插入新对象的风险,而其他上下文在其插入过程的中途。
补充意见:
考虑到问题和SQLite限制,在我的应用程序中,我们采用了一个框架,根据NSURLConnection
,Web调用自然是并发的,随后解析结果(JSON解析加上一些捕获到结果)同时发生,然后将查找或创建步骤引导到一个串行队列中。
序列化会丢失很少的处理时间,因为无论如何SQLite行程都会序列化,并且它们是绝大多数序列化的东西。
答案 1 :(得分:3)
首先在您的操作之间创建依赖关系。确保在依赖之前无法完成。
每个操作都应在完成后调用save。接下来,我将尝试在此处建议的Find-Or-Create方法:
它可以解决你的重复问题,并且可能导致你做更少的取物(这是昂贵和缓慢的,因此快速耗尽电池)。
您还可以创建一个全局子上下文来处理所有导入,然后在最后合并整个巨大的东西,但这实际上取决于数据集的大小和内存考虑因素。
答案 2 :(得分:2)
我一直在努力解决同样的问题。到目前为止,关于这个问题的讨论给了我一些想法,我现在将分享这些想法。
请注意,这实际上是未经测试的,因为在我的情况下,我只是在测试期间很少看到这个重复的问题,而且我没有明显的方法可以轻松地重现它。
我有相同的CoreData堆栈设置 - 私有队列上的主MOC,它在主队列上有一个子节点,它用作应用程序的主上下文。最后,使用后台队列将批量导入操作(查找或创建)传递到第三个MOC。操作完成后,保存将传播到PSC。
我已将我的所有Core Data堆栈从AppDelegate移动到一个单独的类(AppModel
),该类为应用程序提供对域的聚合根对象(Player
)的访问权限。帮助函数,用于对模型执行后台操作(performBlock:onSuccess:onError:
)。
幸运的是,所有主要的CoreData操作都是通过这种方法进行的,所以如果我能确保这些操作是串行运行的,那么应该解决重复的问题。
- (void) performBlock: (void(^)(Player *player, NSManagedObjectContext *managedObjectContext)) operation onSuccess: (void(^)()) successCallback onError:(void(^)(id error)) errorCallback
{
//Add this operation to the NSOperationQueue to ensure that
//duplicate records are not created in a multi-threaded environment
[self.operationQueue addOperationWithBlock:^{
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[managedObjectContext setUndoManager:nil];
[managedObjectContext setParentContext:self.mainManagedObjectContext];
[managedObjectContext performBlockAndWait:^{
//Retrive a copy of the Player object attached to the new context
id player = [managedObjectContext objectWithID:[self.player objectID]];
//Execute the block operation
operation(player, managedObjectContext);
NSError *error = nil;
if (![managedObjectContext save:&error])
{
//Call the error handler
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", error);
if(errorCallback) return errorCallback(error);
});
return;
}
//Save the parent MOC (mainManagedObjectContext) - WILL BLOCK MAIN THREAD BREIFLY
[managedObjectContext.parentContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext.parentContext save:&error])
{
//Call the error handler
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", error);
if(errorCallback) return errorCallback(error);
});
return;
}
}];
//Attempt to clear any retain cycles created during operation
[managedObjectContext reset];
//Call the success handler
dispatch_async(dispatch_get_main_queue(), ^{
if (successCallback) return successCallback();
});
}];
}];
}
我在这里添加的内容,我希望能为我解决问题的方法是将整个内容包装在addOperationWithBlock
中。我的操作队列配置如下:
single.operationQueue = [[NSOperationQueue alloc] init];
[single.operationQueue setMaxConcurrentOperationCount:1];
在我的API类中,我可能会按如下方式对我的操作执行导入:
- (void) importUpdates: (id) methodResult onSuccess: (void (^)()) successCallback onError: (void (^)(id error)) errorCallback
{
[_model performBlock:^(Player *player, NSManagedObjectContext *managedObjectContext) {
//Perform bulk import for data in methodResult using the provided managedObjectContext
} onSuccess:^{
//Call the success handler
dispatch_async(dispatch_get_main_queue(), ^{
if (successCallback) return successCallback();
});
} onError:errorCallback];
}
现在NSOperationQueue
已经到位,不再可能同时进行多个批处理操作。