5

典型的なマルチコンテキスト 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
4

0 に答える 0