使用GCD和Core Data会导致崩溃

时间:2014-02-19 17:51:13

标签: objective-c multithreading magicalrecord

我从服务器检索数据,我需要处理它。

对于每个密钥,我创建一个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“集”中,我不会将其添加到NSArrayNSDictionaryNSSet。但我不相信我列举了那一套。我通过创建新的边界对象来改变它。

我认为还有一些我不理解或尚未掌握的事情。

有什么想法吗?

更新:

    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被记录两次,因此不应该重用线程。

2 个答案:

答案 0 :(得分:0)

托管对象上下文不是线程安全的,您不得使用它们或任何托管对象/集/数据结构/它们可能从不同的队列或线程返回的任何内容。

我不知道魔法记录是否支持它,但你肯定应该为你的上下文使用队列包含,然后你必须在MOC的私有队列的上下文中做所有事情。

如果使用线程包含,则必须保证上下文创建的上下文和任何托管对象始终是串行访问的,您在上面的代码中绝对不会这样做。

答案 1 :(得分:0)

你的问题是gcd队列线程不是一对一的映射。 GCD重用线程,因此你可能在不知不觉中跨越线程边界。我的建议是简单地创建一个新的上下文并停止使用contextForCurrentThread。我写了关于问题的更多细节on my blog。 ContextForCurrentThread将在即将发布的版本中删除。