【发布时间】:2014-09-10 04:37:55
【问题描述】:
我对 Core Data 多线程保存有点困惑。
我有以下NSManagedObjectContext 设置(与MagicalRecord 相同):
SavingContext (NSPrivateQueueConcurrencyType) has child DefaultContext(NSMainQueueConcurrencyType)
每个保存线程都有自己的上下文 (NSPrivateQueueConcurrencyType),DefaultContext 作为父线程。
所以问题是:如果我需要保证唯一性,我该如何依赖在不同线程上保存相同的类型?
这是一个小测试示例(Test 是 NSManagedObject 的子类):
@implementation Test
+ (instancetype) testWithValue:(NSString *) str {
[NSThread sleepForTimeInterval:3];
Test *t = [Test MR_findFirstByAttribute:@"uniqueField" withValue:str];
if (!t) {
NSLog(@"No test found!");
t = [Test MR_createEntity];
}
t.uniqueField = str;
return t;
}
@end
它首先检查新创建的线程上下文中是否有Test(它具有父级DefaultContext),如果没有,则在当前线程上下文中创建它。
这里是测试代码:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;
[queue addOperationWithBlock:^{
[Test operationWithValue:@"1"];
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
}];
[queue addOperationWithBlock:^{
[Test operationWithValue:@"1"];
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Total tests: %lu", (unsigned long)[Test MR_countOfEntities]);
[Test MR_truncateAll];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
});
它只运行两个操作,并尝试保存相同的数据。创建测试后,我保存所有上下文(当前线程、默认上下文和根保存上下文)。大多数时候会有2个测试。您可以修改和添加信号量以确保两个线程同时进行检查。
2014 年 9 月 20 日更新
我已经添加了 mitrenegade 提供的代码(见下文),现在我的 Test.m 有了一个功能:
-(BOOL)validateUniqueField:(id *)ioValue error:(NSError **)outError {
// The property being validated must not already exist
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([self class])];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"uniqueField == %@ AND self != %@", *ioValue, self];
int count = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
if (count > 0) {
NSLog(@"Thread: %@ (isMain: %hhd), Validation failed!", [NSThread currentThread], [NSThread isMainThread]);
return NO;
}
NSLog(@"Thread: %@ (isMain: %hhd), Validation succeeded!", [NSThread currentThread], [NSThread isMainThread]);
return YES;
}
两个线程创建相同的值(测试示例在帖子的开头)我有以下输出:
2014-09-20 11:48:53.824 coreDataTest[892:289814] Thread: <NSThread: 0x15d38940>{number = 3, name = (null)} (isMain: 0), Validation succeeded!
2014-09-20 11:48:53.826 coreDataTest[892:289815] Thread: <NSThread: 0x15e434a0>{number = 2, name = (null)} (isMain: 0), Validation succeeded!
2014-09-20 11:48:53.830 coreDataTest[892:289750] Thread: <NSThread: 0x15e172c0>{number = 1, name = main} (isMain: 1), Validation failed!
2014-09-20 11:48:53.833 coreDataTest[892:289750] Thread: <NSThread: 0x15e172c0>{number = 1, name = main} (isMain: 1), Validation failed!
2014-09-20 11:48:53.837 coreDataTest[892:289750] Thread: <NSThread: 0x15e172c0>{number = 1, name = main} (isMain: 1), Validation failed!
2014-09-20 11:48:53.839 coreDataTest[892:289750] Thread: <NSThread: 0x15e172c0>{number = 1, name = main} (isMain: 1), Validation failed!
2014-09-20 11:48:56.251 coreDataTest[892:289750] Total tests: 2
但是如果我查看底层的 sqlite 文件,根本就没有记录(这意味着它们卡在主上下文中)
【问题讨论】:
-
不要再使用 contextForCurrentThread。您的应用会崩溃,而且是随机且难以追踪的。 NSOperationQueue 现在是使用 GCD 实现的。这意味着并发模型基于队列,而不是线程。可以使用单个队列(轻松地)跨越线程边界。底线,停止使用 contextForCurrentThread
-
@casademora 所以正确的方法是将
NSManagedObjectContext传递给每个模型构造函数并使用您答案中的模式? -
@user2786037 您应该停止使用
MR_contextForCurrentThread,因为它不能保证您不会在超过 1 个线程上运行代码(请参阅 casademora 答案)。这是他博客上的文章:saulmora.com/2013/09/15/…
标签: ios multithreading core-data magicalrecord