典型的なマルチコンテキスト CoreData スタックのセットアップがあります。メイン キューに子があり、アプリのメイン コンテキストとして使用される、プライベート キュー (PSC に接続されている) 上のマスター MOC です。最後に、一括インポート操作 (検索または作成) は、バックグラウンド キュー (操作ごとに作成された新しいコンテキスト) を使用して 3 番目の MOC に渡されます。操作が完了すると、保存が PSC まで伝搬されます。
おそらくこれらの操作が同時に実行されていたため、重複したオブジェクトが作成されるという問題に苦しんでいました。オペレーション 1 が開始されます。オブジェクトが存在しない場合は、操作 1 で作成されました。同時に、操作 2 が同時に実行されています。操作 1 が完了していないため、操作 2 は新しく作成されたオブジェクトを認識できなかった可能性があり、新しいオブジェクトの作成にも進むことになり、重複が発生します。
これを解決するために、すべての検索または作成操作をシリアルNSOperationQueue
に注ぎ込み、すべての操作が一度に 1 つずつ実行されるようにしました。
- (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
同じオブジェクトを持つ複製オブジェクトがストアに入ると、このアルゴリズムは機能しなくなり、複製が増殖することに注意してください。しかし、それは私が心配している問題ではなく、そもそも重複が発生しているという事実です。
2 番目の更新:
これは、重複がストアに入る直前に発生しているように見えるクラッシュのスタック トレースです。これは問題を絞り込むのに役立ちますか?
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