5

我有一个典型的多上下文 CoreData 堆栈设置 - 私有队列上的主 MOC(附加到 PSC),它在主队列上有一个子队列,它用作应用程序的主上下文。最后,使用后台队列(为每个操作创建新上下文)将批量导入操作(查找或创建)传递到第三个 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包含一个查找或创建的实现:

- (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
4

0 回答 0