我从服务器检索数据,我需要处理它。
对于每个密钥,我创建一个NSManagedObject
。每个对象都在相同的上下文中创建。我正在使用魔法记录。
-(id)init {
if (self = [super init]){
self.context = [NSManagedObjectContext MR_contextForCurrentThread];
}
return self;
}
线程:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
for (id key in boundariesDictionary) {
dispatch_group_async(group, queue, ^{
DLog(@"nsthread: %@", [NSThread currentThread]);
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
DLog(@"boundaryIDString: %@", boundaryIDString)
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:@"boundaryID == %@ AND api == %@", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:self.context];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
DLog(@"creating boundary %@", boundaryIDString);
boundary = [Boundary MR_createInContext:self.context];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
}
}
}
[self processBoundary]只需获取字典并将其设置为托管对象的属性。
if ([boundaryDictionary objectForKey:@"name"]) {
boundary.name = [boundaryDictionary objectForKey:@"name"];
}
//more data processing
这会导致错误:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x1776f7f0> was mutated while being enumerated.'
如果我不为每个线程使用相同的上下文,它运行正常。
我不明白我所枚举的NSDictionary
boundaryDictionary之外的其他设置。我根本没有改变boundaryDictionary,只是将数据复制到核心数据中。
当我PO对象(在这种情况下为0x1776f7f0)时,我得到一组Boundary
个对象的列表。这些Boundary
对象只存在于NSManagedObjectContext
“集”中,我不会将其添加到NSArray
,NSDictionary
或NSSet
。但我不相信我列举了那一套。我通过创建新的边界对象来改变它。
我认为还有一些我不理解或尚未掌握的事情。
有什么想法吗?
更新:
for (id key in boundariesDictionary) {
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DLog(@"saveWithBlock thread: %@", [NSThread currentThread]);
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:@"boundaryID == %@ AND api == %@", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:localContext];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
boundary = [Boundary MR_createInContext:localContext];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
if (boundary == nil) {
//Prompt Error
}
else {
for (NSNumber *groupID in groupIDs) {
if ([groupID isKindOfClass:[NSNumber class]]) {
Group *group = [Group MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:@"groupID == %@ OR groupID == 0 AND api == %@", groupID, self.serverCall.API]];
if (group != nil) {
group.lastUpdated = [NSDate date];
[group addBoundariesObject:boundary];
}
else {
DLog(@"group %@ DNE", groupID);
}
}
}
}
}
} completion:^(BOOL success, NSError *error) {
DLog(@"saveWithBlock completion Block | time: %f", [[NSDate date] timeIntervalSinceDate:startTime]);
}];
}
}
所以对于我的Group
29应该看到我正在创建的所有边界,但事实并非如此。它不一致。有时看到所有,有时是一些,有时看不到。
另外,我经常看到
NO CHANGES IN ** BACKGROUND SAVING (ROOT) ** CONTEXT - NOT SAVING
在日志中。它与我看到的这些消息中有多少不一致,而保存的上下文将插入多于1个对象。
不确定它是否应该如何表现,如果每个块都有自己的上下文,并且每个块只创建1个对象,那么它似乎应该是1:1比率。
我记录每个线程ID,并为每个块创建一个新线程。没有线程ID被记录两次,因此不应该重用线程。
答案 0 :(得分:0)
托管对象上下文不是线程安全的,您不得使用它们或任何托管对象/集/数据结构/它们可能从不同的队列或线程返回的任何内容。
我不知道魔法记录是否支持它,但你肯定应该为你的上下文使用队列包含,然后你必须在MOC的私有队列的上下文中做所有事情。
如果使用线程包含,则必须保证上下文创建的上下文和任何托管对象始终是串行访问的,您在上面的代码中绝对不会这样做。
答案 1 :(得分:0)
你的问题是gcd队列线程不是一对一的映射。 GCD重用线程,因此你可能在不知不觉中跨越线程边界。我的建议是简单地创建一个新的上下文并停止使用contextForCurrentThread。我写了关于问题的更多细节on my blog。 ContextForCurrentThread将在即将发布的版本中删除。