objective c - Ios CoreData with 3 MOC solution (app freezes on saving process) -
i use coredata in app, 3 contexts:
__mastermanagedobjectcontext -> context has nspersistentstorecoordinator , save data disk.
_mainmanagedobjectcontext -> context used app, everywhere
dispatchcontext -> context used in background method, have webservice access , coredata insertion/update stuff.
i'll put code realize solution:
app initialisation code:
- (bool)application:(uiapplication *)application didfinishlaunchingwithoptions:(nsdictionary *)launchoptions //a app começa aqui { nspersistentstorecoordinator *coordinator = [self newpersistentstorecoordinator]; __mastermanagedobjectcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsprivatequeueconcurrencytype]; [__mastermanagedobjectcontext setpersistentstorecoordinator:coordinator]; _mainmanagedobjectcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsmainqueueconcurrencytype]; [_mainmanagedobjectcontext setundomanager:nil]; [_mainmanagedobjectcontext setparentcontext:__mastermanagedobjectcontext]; return yes; }
method create new store cordinator
- (nspersistentstorecoordinator *)newpersistentstorecoordinator { nsurl *storeurl = [[self applicationdocumentsdirectory] urlbyappendingpathcomponent:@"example.sqlite"]; nserror *error = nil; nspersistentstorecoordinator *pc = [[nspersistentstorecoordinator alloc] initwithmanagedobjectmodel:[self newmanagedobjectmodel]]; nsdictionary *options = [nsdictionary dictionarywithobjectsandkeys: [nsnumber numberwithbool:yes], nsmigratepersistentstoresautomaticallyoption, [nsnumber numberwithbool:yes], nsinfermappingmodelautomaticallyoption, nil]; if (![pc addpersistentstorewithtype:nssqlitestoretype configuration:nil url:storeurl options:options error:&error]) { nslog(@"unresolved error %@, %@", error, [error userinfo]); abort(); } return pc; } - (nsmanagedobjectmodel *)newmanagedobjectmodel { nsurl *modelurl = [[nsbundle mainbundle] urlforresource:@"example" withextension:@"momd"]; nsmanagedobjectmodel *newmanagedobjectmodel = [[nsmanagedobjectmodel alloc] initwithcontentsofurl:modelurl]; return newmanagedobjectmodel; }
thread call context specification (essential code):
@try { dispatchcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsconfinementconcurrencytype]; [dispatchcontext setundomanager:nil]; [dispatchcontext setparentcontext:__mastermanagedobjectcontext]; nsnotificationcenter *notify = [nsnotificationcenter defaultcenter]; [notify addobserver:self selector:@selector(mergechanges:) name:nsmanagedobjectcontextdidsavenotification object:dispatchcontext]; if(dispatchcontext != nil) { [nsthread detachnewthreadselector:@selector(parsedatawithobjects) totarget:self withobject:nil]; } else { nslog(@"context nil"); } }
the background method:
- (void)parsedatawithobjects { [dispatchcontext lock]; ... webservice data parse, , core data inserting/updating (+/- 5mb) ... [dispatchcontext save:&error]; [dispatchcontext unlock]; [__mastermanagedobjectcontext save:nil]; }
this method caled in ui access coredata data.
- (nsmanagedobjectcontext *)managedobjectcontext { return _mainmanagedobjectcontext; }
a calling example:
nsmanagedobjectcontext *context = [(appdelegate *)[[uiapplication sharedapplication] delegate] managedobjectcontext]; ...fetching, update, ...
now, problem:
whenever master context saved ([__mastermanagedobjectcontext save:nil];
, on background), when try access main context (_mainmanagedobjectcontext
), app freezes (maybe lock?).
the saving process takes long time (because lots of data (aprox. 6mb)). while saving, app turns slow , if access data while process running, app freezes forever (i need force quit).
another problem merge contexts. imagine, using main context in other viewcontroller, , saving context, works fine until close de app. when open app again, nothing saved.
what i'm doing wrong? until now, contexts confusing me. can me? appreciate :)
---------- edit:
following florian kugler response, have 2 context, every 1 same coordinator.
when app initialised, call method:
-(void) createcontexts { nsurl *modelurl = [[nsbundle mainbundle] urlforresource:@"example" withextension:@"momd"]; nsmanagedobjectmodel *newmanagedobjectmodel = [[nsmanagedobjectmodel alloc] initwithcontentsofurl:modelurl]; nsurl *storeurl = [[self applicationdocumentsdirectory] urlbyappendingpathcomponent:@"example.sqlite"]; nserror *error = nil; pc = [[nspersistentstorecoordinator alloc] initwithmanagedobjectmodel:newmanagedobjectmodel]; nsdictionary *options = [nsdictionary dictionarywithobjectsandkeys: [nsnumber numberwithbool:yes], nsmigratepersistentstoresautomaticallyoption, [nsnumber numberwithbool:yes], nsinfermappingmodelautomaticallyoption, nil]; if (![pc addpersistentstorewithtype:nssqlitestoretype configuration:nil url:storeurl options:options error:&error]) { nslog(@"unresolved error %@, %@", error, [error userinfo]); abort(); } mainmanagedobjectcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsmainqueueconcurrencytype]; mainmanagedobjectcontext.persistentstorecoordinator = pc; } - (void)mergechanges:(nsnotification*)notification { [mainmanagedobjectcontext performblock:^{ [mainmanagedobjectcontext mergechangesfromcontextdidsavenotification:notification]; }]; } - (void)savemastercontext { [mainmanagedobjectcontext performblock:^{ [mainmanagedobjectcontext save:nil]; }]; }
to start data import (on background), use code:
nsnotificationcenter *notify = [nsnotificationcenter defaultcenter]; [notify addobserver:self selector:@selector(mergechanges:) name:nsmanagedobjectcontextdidsavenotification object:backgroundcontext]; [nsthread detachnewthreadselector:@selector(parsedatawithobjects) totarget:self withobject:nil];
my background method:
- (void)parsedatawithobjects { [self resettime]; backgroundcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsprivatequeueconcurrencytype]; backgroundcontext.persistentstorecoordinator = pc; ... [backgroundcontext save:&error]; }
resuming...
- 1st - create main context
- 2nd - define background context notification, merge changes after save.
- 3rd - call background method
- 4rd - save background context
and performance better. app freezes little, think on "mergechanges". i'm doing wrong?
when using nsprivatequeueconcurrencytype
or nsmainqueueconcurrencytype
should wrap on these contexts in performblock:
. makes sure, these commands executed on right queue. example when save master context:
[__mastermanagedobjectcontext performblock:^{ [__mastermanagedobjectcontext save]; }];
furthermore don't have merge changes manually dispatch context if set child master context. save child context changes pushed parent context.
another problem initializing context nsconfinementconcurrencytype on 1 thread , use on different thread. concurrency type important initialize context on thread going use it.
however, suggest don't use nsconfinementconcurrencytype @ all. 1 possible alternative setup dispatch context nsprivatequeueconcurrencytype
:
nsmanagedobjectcontext* dispatchcontext = [[nsmanagedobjectcontext] alloc] initwithconcurrencytype:nsprivatequeueconcurrencytype]; dispatchcontext.parentcontext = __mastermanagedobjectcontext; [dispatchcontext performblock:^{ [self parsedatawithobjects]; }];
when there no need acquire locks, within block processed on private serial queue.
performance
you mentioned saving takes quite long , app becomes unresponsive.
your managed object context setup 3 nested contexts (private <- main <- dispatch) not best choice if plan import larger amounts of data in dispatch context. block main thread significant amounts of time, because changes make in dispatch context have copied main context before can saved in "root" context.
i wrote article this, comparing performance of different core data setups. in follow post explain in greater detail why setup takes time on main thread.
for importing large amounts of data faster use independent managed object contexts common persistent store coordinator. create 1 context nsmainqueueconcurrencytype
(which use ui related stuff), , 1 nsprivatequeueconcurrencytype
(for importing data). assign same persistent store coordinator both of them.
// assuming have persistendstorecoordinator nsmanagedobjectcontext* maincontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsmainqueueconcurrencytype]; maincontext.persistentstorecoordinator = persistentstorecoordinator; nsmanagedobjectcontext* backgroundcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsprivatequeueconcurrencytype]; backgroundcontext.persistentstorecoordinator = persistentstorecoordinator;
this setup doesn't provide automatic change propagation nested contexts, easy via save notification. notice use of performblock:
again, merging happens on right thread:
// ... nsnotificationcenter *notify = [nsnotificationcenter defaultcenter]; [notify addobserver:self selector:@selector(mergechanges:) name:nsmanagedobjectcontextdidsavenotification object:backgroundcontext]; // ... - (void)mergechanges:(nsnotification*)notification { [maincontext performblock:^{ [maincontext mergechangesfromcontextdidsavenotification:notification]; }]; }
hope helps!
Comments
Post a Comment