Reputation: 948
i have an app that Syncs data to a server. The process is: 1) Convert the local data to JSON 2) Send the local data to the server in a HTTP Post 3) process the response from the server from the HTTP Post in 2) -0 i.e. its the confirmation form the server it did it's processing, and has saved everything OK 4) do a HTTP request for JSON from the server for job updates 5) process this JSON saving to core data
i have tried putting various parts on a background thread so i can release the UI. I keep having intermittent problems, and i think this is because i am using core data on a background thread.
This process is kicked off from a button click, so to keep the UI working i need to do at least some of it in a background thread.
At the moment i send the initial method call to a background thread, and process it all in the background, updating the UI with progress message on the main thread.
can i keep it all as is, but force the core data sections onto the main thread? is that even a good solution? Is it OK to keep everything tin eh background and just put the core core data bits back onto the main thread?
thanks!
edit - I have now made the code more sensible - the VC now kicks off the processing onto a background thread and update the UI via protocol calls from 3 classes that are used to send and download the data.
Each class has it's own managed object context object, but each class uses the following code to init:
-(NSManagedObjectContext*) managedObjectContext
{
if (!_managedObjectContext)
{
mavisFireChecksAppDelegate *appDelegate = (mavisFireChecksAppDelegate *) [[UIApplication sharedApplication] delegate];
_managedObjectContext=appDelegate.managedObjectContext;
}
return _managedObjectContext;
}
should i change this? the classes are being created on a background thread and although they stay on that thread, the appdeleegate.managedObjectContext is created on app load, so on the main thread. Do i have this correct in my head now? the 3 classes should all init a new MOC not use the appDelegate managed object context as they are running on the background, and the app delegate is created on the main thread?
without actually changing the managed object context init not the classes, it is still erroring once in every 50-100 calls or so. is using the appldelegate.managedobject in the background object the likely cause of the issue
Edit****
Accepted solution in thread below, but some additional useful info for anyone reading this :
I'd just like to add to this incase anyone else ever reads it. I did wrap all CD calls in perform... blocks, but introduced a new error because I wasn't merging the changes correctly on the 2 separate MOC. the link provided by daniel - robots.thoughtbot.com/core-data
, is brilliant and showed me my mistake. There is also a CD stack that you can lift off git hub linked at the bottom of the article.
Upvotes: 0
Views: 82
Reputation: 24247
Calling Core Data from a background thread does not mean that you have achieved concurrency. Consider this code that creates a background thread context:
- (NSManagedObjectContext *)createBackgroundContextWithStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator {
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setPersistentStoreCoordinator:coordinator];
context.undoManager = nil;//if you don't need an undo manager always do this for performance boosts
return context;
}
You will see that the context has a concurrency type of NSPrivateQueueConcurrencyType
whereas your UI stuff will typically have NSMainQueueConcurrencyType
Now that you have a background context you should be aware of the performBlock: (asynchronous) and performBlockAndWait: (synchronous) calls for transacting with Core Data. So your import might look like this:
- (void)importStuff:(NSArray *)stuffToImport
usingContext:(NSManagedObjectContext *)context
andPerformBlock:(void (^)(BOOL madeChanges))completionBlock {
[context performBlock:^{
/**
* do some import stuff
*/
BOOL madeChanges = [context hasChanges];
[context save:nil];
if (completionBlock) {
completionBlock(madeChanges);
}
}];
}
where you would call the import method as follows:
NSArray *stuff = //whatever you fetched from server;
NSManagedObjectContext *context = //background context;
[importer importStuff:stuff
usingContext:context
andPerformBlock:^(BOOL madeChanges){
if (madeChanges){
//reload UI
}
}];
Remember, Managed Objects are NOT thread-safe. You should always make sure to perform any updates, accesses, or inserts into a contexts performBlock or performBlockAndWait.
Generally I find that blocks are more useful for imports than delegate methods:)
Upvotes: 1