ios - Multi-Context Core Data - Duplicate issues when importing from a background context -
i have typical multi-context coredata stack setup - master moc on private queue (attached psc), has child on main queue , used app's main context. finally, bulk import operations (find-or-create) passed off onto third moc using background queue (new context created each operation). once operation complete saves propagated psc.
i'd been suffering issues duplicate objects being created, presumably because these operations executing concurrently. operation 1 start. if object didn't exist, created in operation 1. @ same time operation 2 concurrently running. because operation 1 hadn't completed, operation 2 couldn't possibly know newly created object, proceed create new object, hence duplicates.
to resolve this, funnelled find-or-create operation serial nsoperationqueue
ensure operations executed 1 @ time:
- (void) performblock: (void(^)(player *player, nsmanagedobjectcontext *managedobjectcontext)) operation onsuccess: (void(^)()) successcallback onerror:(void(^)(id error)) errorcallback { //add operation nsoperationqueue ensure //duplicate records not created in multi-threaded environment [self.operationqueue addoperationwithblock:^{ nsmanagedobjectcontext *managedobjectcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsprivatequeueconcurrencytype]; [managedobjectcontext setundomanager:nil]; [managedobjectcontext setparentcontext:self.mainmanagedobjectcontext]; [managedobjectcontext performblockandwait:^{ //retrive copy of player object attached new context id player = [managedobjectcontext objectwithid:[self.player objectid]]; //execute block operation operation(player, managedobjectcontext); nserror *error = nil; if (![managedobjectcontext save:&error]) { //call error handler dispatch_async(dispatch_get_main_queue(), ^{ nslog(@"%@", error); if(errorcallback) return errorcallback(error); }); return; } //save parent moc (mainmanagedobjectcontext) - block main thread breifly [managedobjectcontext.parentcontext performblockandwait:^{ nserror *error = nil; if (![managedobjectcontext.parentcontext save:&error]) { //call error handler dispatch_async(dispatch_get_main_queue(), ^{ nslog(@"%@", error); if(errorcallback) return errorcallback(error); }); return; } }]; //attempt clear retain cycles created during operation [managedobjectcontext reset]; //call success handler dispatch_async(dispatch_get_main_queue(), ^{ if (successcallback) return successcallback(); }); }]; }]; }
my operation queue configured follows:
single.operationqueue = [[nsoperationqueue alloc] init]; [single.operationqueue setmaxconcurrentoperationcount:1];
this seem have reduced incidences of duplicate issues quite surprise has not eliminated entirely. occurrences less frequent, still happens.
can enlighten me why still happening?
update: added additional code describe happening in operation
block:
- (void) importupdates: (id) methodresult onsuccess: (void (^)()) successcallback onerror: (void (^)(id error)) errorcallback { [_model performblock:^(player *player, nsmanagedobjectcontext *managedobjectcontext) { //player , opponents nsmutabledictionary *opponents = [nsmutabledictionary dictionary]; //store opponents in dict can relationship fix-up later [player parsedictionary:methodresult[@"player"] inmanagedobjectcontext:managedobjectcontext]; (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
contains implementation of 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 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 existing match array , matches in 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 match opponent (as may have been initally nil in case of 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 match player , 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 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]; }
it's worth noting once duplicate objects same matchid
find way store algorithm no longer works , duplicates proliferated. that's not problem i'm concerned about, it's fact duplicates occurring in first place.
second update:
this stack trace crash i'm seeing seems occur before duplicates store. narrow down issue?
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
Comments
Post a Comment