多上下文核心数据 - 从后台上下文导入时出现重复问题

时间:2013-08-28 11:48:36

标签: ios multithreading core-data

我有一个典型的多上下文CoreData堆栈设置 - 私有队列(连接到PSC)上的主MOC,它在主队列上有一个子节点,它用作应用程序的主上下文。最后,使用后台队列(为每个操作创建的新上下文)将批量导入操作(查找或创建)传递到第三个MOC。操作完成后,保存将传播到PSC。

我遇到了创建重复对象的问题,可能是因为这些操作同时执行。操作1将开始。如果对象不存在,则在操作1中创建。同时,操作2同时运行。因为操作1还没有完成,操作2不可能知道新创建的对象,所以它也会继续创建一个新对象,因此重复。

要解决此问题,我将所有find-or-create操作汇集到一个序列NSOperationQueue中,以确保一次执行所有操作:

- (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();
            });
        }];
    }];
}

我的操作队列配置如下:

single.operationQueue = [[NSOperationQueue alloc] init];
[single.operationQueue setMaxConcurrentOperationCount:1];

这确实显着降低了重复问题的发生率,但令我惊讶的是并没有完全消除它。事件的发生频率要低得多,但仍然会发生。

任何人都可以告诉我为什么还会这样吗?

更新:添加了其他代码来描述operation块中发生的事情:

- (void) importUpdates: (id) methodResult onSuccess: (void (^)()) successCallback onError: (void (^)(id error)) errorCallback
{
    [_model performBlock:^(Player *player, NSManagedObjectContext *managedObjectContext) {
        //Player and Opponents
        NSMutableDictionary *opponents = [NSMutableDictionary dictionary]; //Store the opponents in a dict so we can do relationship fix-up later

        [player parseDictionary:methodResult[@"player"] inManagedObjectContext:managedObjectContext];

        for (NSDictionary *opponentDictionary in methodResult[@"opponents"])
        {
            Opponent *opponent = [Opponent updateOrInsertIntoManagedObjectContext:managedObjectContext withDictionary:opponentDictionary];
            opponents[opponent.playerID] = opponent;
        }

        //Matches
        [self parseMatches: methodResult[@"matches"] withPlayer: player andOpponents: opponents usingManagedObjectContext: managedObjectContext];

    } onSuccess:successCallback onError:errorCallback];
}

parseMatches包含find-or-create:

的实现
- (NSArray *) parseMatches: (NSArray *) matchDictionaries withPlayer: (Player *) player andOpponents: (NSDictionary *) opponents usingManagedObjectContext: (NSManagedObjectContext *) managedObjectContext
{
    NSMutableArray *parsedMatches = [NSMutableArray array];

    [managedObjectContext performBlockAndWait:^{
        //Sorted matchDictionaties
        NSArray *sortedMatchDictionaries = [matchDictionaries sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"matchID" ascending:YES]]];
        NSArray *sortedMatchIDsInResponse = [sortedMatchDictionaries valueForKeyPath:@"matchID"];

        //Fetch the existing matches
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Match"];
        fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(matchID IN %@)", sortedMatchIDsInResponse];
        [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"matchID" ascending:YES]]];
        [fetchRequest setRelationshipKeyPathsForPrefetching:@[@"roundsSet", @"player", @"opponent"]];

        NSError *error;
        NSArray *existingMatches = [managedObjectContext executeFetchRequest:fetchRequest error:&error];


        //Walk through the existing match array and the matches in the response
        int i=0,j=0,updated=0,added=0,skipped=0;
        while ((i < [sortedMatchDictionaries count]))
        {
            NSDictionary *matchDictionary = sortedMatchDictionaries[i];
            Match *match = j < [existingMatches count] ? existingMatches[j] : nil;

            NSLog(@"%@ %@", matchDictionary[@"matchID"], match.matchID);

            NSComparisonResult result = match ? [((NSString *)matchDictionary[@"matchID"]) compare: match.matchID] : NSOrderedDescending;

            if (result == NSOrderedSame)
            {
                //match exists in both, update
                NSLog(@"updated");

                [match parseDictionary:matchDictionary inManagedObjectContext:managedObjectContext];

                //Set the match opponent (as it may have been initally nil in the case of a random match)
                if (match.opponentID != nil && [opponents objectForKey:match.opponentID] != nil)
                {
                    [match setValue:opponents[match.opponentID] forKey:@"opponent"];
                }

                [parsedMatches addObject:match];
                i++,j++,updated++;
            }
            else if (result == NSOrderedDescending)
            {
                NSLog(@"added");

                //match doesnt exist on device, add
                Match *match = [Match insertNewObjectWithDictionary:matchDictionary inManagedObjectContext:managedObjectContext];

                //Set the match player and opponent
                if (match.opponentID != nil && [opponents objectForKey:match.opponentID] != nil)
                {
                    [match setValue:opponents[match.opponentID] forKey:@"opponent"];
                }
                [match setValue:player forKey:@"player"];

                [parsedMatches addObject:match];
                i++,added++;

            } else {
                NSLog(@"match %@ exists on device but not in response, skipping", match.matchID);
                j++;skipped++;
            }
        }

        NSLog(@"CORE DATA IMPORT: Inserted %u matches. Updated %u matches. Skipped %u matches", added, updated, skipped);
    }];

    return [NSArray arrayWithArray:parsedMatches];
}

值得注意的是,一旦具有相同matchID的重复对象进入商店,那么此算法将不再有效,并且重复项会激增。但这不是我关心的问题,而是事实上重复是首先发生的。

第二次更新:

这是崩溃的堆栈跟踪,我偶尔会看到它似乎发生在重复进入商店之前。这有助于缩小问题范围吗?

SIGSEGV
CoreData_PFfastQueueRelease


0   WIT Premium 0x0019e36e  testflight_backtrace
1   WIT Premium 0x0019da02  TFSignalHandler
2   libsystem_c.dylib   0x3afd7e92  _sigtramp
3   CoreData    0x32d06de8  _PFfastQueueRelease
4   CoreData    0x32ce6eec  -[NSManagedObject release]
5   CoreFoundation  0x32e40310  CFRelease
6   CoreFoundation  0x32f1b433  __CFBasicHashDrain
7   CoreFoundation  0x32e403d0  CFRelease
8   CoreData    0x32cb0d0e  -[_NSFaultingMutableSet dealloc]
9   CoreData    0x32cb51a4  -[_NSNotifyingWrapperMutableSet dealloc]
10  libobjc.A.dylib 0x3ab56488  _ZN12_GLOBAL__N_119AutoreleasePoolPage3popEPv
11  CoreFoundation  0x32e42440  _CFAutoreleasePoolPop
12  Foundation  0x3378c6da  -[__NSOperationInternal start]
13  Foundation  0x33804be2  __block_global_6
14  libdispatch.dylib   0x3af7111e  _dispatch_call_block_and_release
15  libdispatch.dylib   0x3af7f258  _dispatch_root_queue_drain
16  libdispatch.dylib   0x3af7f3b8  _dispatch_worker_thread2
17  libsystem_c.dylib   0x3afa5a10  _pthread_wqthread
18  libsystem_c.dylib   0x3afa58a3  start_wqthread

0 个答案:

没有答案