我已经阅读了一些关于此的博客,但我仍然对如何使用 NSPersistentContainer performBackgroundTask
创建实体并保存它感到困惑。通过在 performBackgroundTask() { (moc) in }
block 中调用便利方法 init(context moc: NSManagedObjectContext)
创建实例后,如果我检查 container.viewContext。 hasChanges
这会返回 false 并表示没有要保存的内容,如果我在 moc
(为此 block 创建的背景 MOC)上调用保存,我会收到如下错误:
fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=( "NSMergeConflict (0x17466c500) for NSManagedObject (0x1702cd3c0) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ... }fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=( "NSMergeConflict (0x170664b80) for NSManagedObject (0x1742cb980) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ...} and new database row = {id = 2; ...}" )}
所以我未能使并发工作,如果有人能向我解释在 iOS 10 中对核心数据使用此功能的正确方法,我将不胜感激
最佳答案
TL:DR:您的问题是您在编写时同时使用了 viewContext
和背景上下文。您应该只以一种同步方式写入核心数据。
完整解释:如果一个对象同时从两个不同的上下文中更改,则核心数据不知道该怎么做。您可以设置一个 mergePolicy 来设置哪个更改应该获胜,但这确实不是一个好的解决方案,因为那样您会丢失数据。许多专业人士长期以来处理这个问题的方法是有一个操作队列来对写入进行排队,这样一次只有一个写入在进行,并且在主线程上有另一个上下文只用于读取.这样你就永远不会遇到任何合并冲突。 (有关此设置的详细解释,请参阅 https://vimeo.com/89370886#t=4223s)。
使用 NSPersistentContainer
进行此设置非常简单。在你的核心数据管理器中创建一个 NSOperationQueue
//obj-c
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;
//swift
let persistentContainerQueue = OperationQueue()
persistentContainerQueue.maxConcurrentOperationCount = 1
并使用此队列进行所有写入:
// obj c
- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
void (^blockCopy)(NSManagedObjectContext*) = [block copy];
[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext* context = self.persistentContainer.newBackgroundContext;
[context performBlockAndWait:^{
blockCopy(context);
[context save:NULL]; //Don't just pass NULL here, look at the error and log it to your analytics service
}];
}]];
}
//swift
func enqueue(block: @escaping (_ context: NSManagedObjectContext) -> Void) {
persistentContainerQueue.addOperation(){
let context: NSManagedObjectContext = self.persistentContainer.newBackgroundContext()
context.performAndWait{
block(context)
try? context.save() //Don't just use '?' here look at the error and log it to your analytics service
}
}
}
当您调用 enqueueCoreDataBlock
时,该 block 将入队以确保没有合并冲突。但是,如果您写入 viewContext
将会破坏此设置。同样,您应该将您创建的任何其他上下文(使用 newBackgroundContext
或使用 performBackgroundTask
)视为只读的,因为它们也不在写入队列中。
起初我以为 NSPersistentContainer
的 performBackgroundTask
有一个内部队列,初步测试支持这一点。经过更多测试后,我发现它也可能导致合并冲突。
关于ios - 用于保存到核心数据的 NSPersistentContainer 并发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42733574/