我有一个典型的多上下文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