Magical Record background save seems to be blocking UI - ios

I have a NSOperation that I put in a queue. The NSOperation does some long running photo processing then I save the information/meta data in core data for that photo. In the main method of my custom NSOperation class is where I execute the below block of code
-(void)main
{
//CODE ABOVE HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
etc...
} completion:^(BOOL success, NSError *error) {
NSLog(#"Done saving");
}];
}
My issue is that even with only 3 photos when it saves it really freezes my UI. I would have thought executing this in the NSOperation I would be fine.
I should add that each NSOperation processes one photo, so at times the queue could have 5-10 photos, but I would not think this would make any difference, even with just three like I said its freezing the UI.
Thank you for the help.
UPDATE:------------*--------------
I switched to version 2.2 but that seems to be blocking the UI even more...also now I'm using
-(void)main
{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
//CODE BELOW HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
[localContext saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
}];
}
This is all done in my NSOperation class, am I doing something wrong?

Don't put the saveWithBlock calls in a background thread. You're effectively creating a background thread from a background thread, which, in this case, is just slowing you down. You should just be able to call saveWithBlock and it should put all your saving code in the background. However, I'm also noticed that you make all your changes in the main UI page of the code, and only call save afterward. This is the wrong usage of this method. You want to do something more like this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//photo processing
//update post from photo processing
} completion:^(BOOL success, NSError *error) {
//This is called when data is in the store, and is called on the main thread
}];
If you do need an NSOperation, I suggest a different pattern:
- (void) main {
NSManagedObjectContext *localContext = [NSManagedObjectContext confinementContext];
// Do your photo stuff here
Post *post = [Post createInContext:localContext];
//more stuff to update post object
[localContext saveToPersistentStoreAndWait];
}

Be careful in how you start the operation.
[operation start]
will start the operation on the current thread, so if you call it from the main thread (which is the UI one) it will block the interface.
You should add the operation to a queue, so that it runs in background without hogging the main thread
[[NSOperationQueue new] addOperation:operation];

Related

NSManagedObjects created on background turning into faults on main thread

I'm dealing with a strange case. I'm getting some data from a API, transform the response JSON into NSManagedObjects and saving them, all that in a background thread and using a NSPrivateQueueConcurrencyType context whose parent is a NSMainQueueConcurrencyType.
At the same time I'm creating the NSManagedObjects, I'm placing them inside an array to use it in my view controller via a completion block executed on the main therad. Here's a simplified version of what I'm doing.
- (void)getObjectFromAPIWithCompletion:((^)(NSArray *objects, NSError *error))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSManagedObjectContext *backgroundMoc = [[CoreDataStack sharedManager] newChildContextWithConcurrencyType:NSPrivateQueueConcurrencyType];
NSArray *items = [self processToParseResponseFromAPIAndSaveInCoreDataUsingContext:backgroundContext];
[backgroundMoc save:nil];
dispatch_async(dispatch_get_main_queue(), ^{
//Beyond this point, all properties of items are nil or 0 (if integer)
if(completion) completion(items, error);
});
});
}
The weird thing happens inside the completion block. I put a breakpoint on the first line of that block and print the items array on the console. Everything looks fine, with all the items (50 in this case) and their properties are OK.
The problem is when that after that line (where I'm not changing anything on the items array) all objects turn into faults and I'm not getting any data on the properties.
Don't know what is the cause of this rare behaviour. Any idea?
Thanks.
EDIT: Hal's answer put me on the right track so here's what I did to fix this problem. Just before calling the block that will be executed on the main thread I "move" the managed objects from the background context to the main context, fetching with their objectIDs. Like this:
NSArray *objects = //Objects created on background thread with Private queue
NSMutableArray *objectsIDs = [NSMutableArray array];
for (Object *object in object) {
[objectsIDs addObject:object.objectID];
}
//Save on private managed object context and on completion...
[self.managedObjectContext saveWithCompletion:^(NSError *error) {
NSManagedObjectContext *mainMOC = [[CoreDataManager sharedManager] mainContext];
NSMutableArray *fetchedObjects = [NSMutableArray array];
for (NSManagedObjectID *objectID in objectsIDs) {
[fetchedArticles addObject:[mainMOC objectWithID:objectID]];
}
if (completion) completion(fetchedObjects, pagination, nil);
}];
This way, all objects are not faults on the main thread.
If those objects (the contents of items) have been created on the background thread, you can't use them in the main thread. You can't pass NSManagedObject instances between threads.
You'll have to pass NSManagedObjectIDs (which won't be permanent until you've saved the background MOC), or use an NSFetchedResultsController (pointing to the main MOC) on the main thread. This is one of the cases that NSFetchedResultsController was designed for, so I recommend you go that route.

How to make api calls synchronously in background?

I have four api calls to make. They should be in following order:
apiSyncDataToCloud;
apiSyncImagesToServer;
apiDeleteDataFromCloud;
apiSyncDataFromCloudInBackground;
Each one of them is to be called irrespective of the fact that previous one finishes successfully or fails.
Also, each one of them have success and failure completion blocks.
In success completion block database is updated.
All this process has to be performed in background and has to be done a no of times.
Api calls are of course performed in background but once a call completes database update is performed on main thread thereby freezing the app.
So, I went with several solutions:
Tried following code:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
[self apiSyncDataToCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncImages];
}];
[queue addOperationWithBlock:^{
[self apiDeleteDataFromCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncDataFromCloudInBackground];
}];
But this only guarantees that api method calls will be performed in order. But their result follows no specific order. That is, method calls will be in the order specified but success block of apiSyncImagesToServer may be called before success block of apiSyncDataToCloud.
Then I went with following solution:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self apiSyncDataToCloud];
});
and in the success and failure blocks of apiSyncDataToCloud I have called apiSyncImagesToServer. This too did'nt work.
Now I am simply going with my last solution. I am just calling apiSyncDataToCloud.
In success completion block this method first updates the database and then calls other api.
In failure completion block this method simply makes the api call without updating the database.
For example-
structure of apiSyncDataToCloud is as follows:
-(void)apiSyncDataToCloud{
NSLog(#"method 1");
NSMutableDictionary *dicDataToBeSynced = [NSMutableDictionary dictionary];
dicDataToBeSynced = [self getDataToBeSynced];
if (dicDataToBeSynced.count!=0) {
if ([[StaticHelper sharedObject] isInternetConnected]) {
[[ApiHandler sharedObject] postRequestWithJsonString:API_SYNC_DATA_TO_CLOUD andHeader:[UserDefaults objectForKey:kAuthToken] forHeaderField:kAccessToken andParameters:dicDataToBeSynced WithSuccessBlock:^(NSURLResponse *response, id resultObject, NSError *error) {
NSLog(#"Data synced successfully to server");
[self updateColumnZSYNC_FLAGForAllTables];//updating db
[self apiSyncImagesToServer];//api call
} andFailureBlock:^(NSURLResponse *task, id resultObject, NSError *error) {
NSLog(#"Data syncing to cloud FAILED");
[self apiSyncImagesToServer];//simply make api call without updating db
}];
}
}else{
[self apiSyncImagesToServer];make api call even if no data to be synced found
}
}
Similary, inside apiSyncImagesToServer I am calling apiDeleteDataFromCloud.....
As a result my problem remained as it is. App freezes when it comes to success block updating db, downloading images...all operations being performed on main thread.
Plz let me know a cleaner and better solution.
You can create your own custom queue and call request one by one.
i.e.
dispatch_queue_t myQueue;//declare own queue
if (!myQueue) {//check if queue not exists
myQueue = dispatch_queue_create("com.queue1", NULL); //create queue
}
dispatch_async(myQueue, ^{[self YOUR_METHOD_NAME];});//call your method in queue block
If you want update some UI after receiving data then update UI on main Thread.
1) Better to use AFNetworking for this kind of situations. Because AFNetworking provides better way to handle Main & Background Threads.
AFNetworking supports success and failure blocks so you can do one by one WS Api calls from success and failure of previous WS Api call. So during this time period show progress HUD. Success of last API then update DB and hide progress HUD.
2) If you need to use NSOperationQueue and NSInvocationOperation
and follow this link. https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift
Api calls are of course performed in background but once a call
completes database update is performed on main thread thereby freezing
the app.
Then why not perform it in a separate queue?
Try using
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//your code
});
to perform time-consuming tasks and
dispatch_async(dispatch_get_main_queue(), ^{
//your code
});
to only update UI.

MagicalRecord: saving context after deleting is too long

I have next problem: I have database with about 7000 of entities, when I need to update them (I have XML file which I parse) for first I delete all entities, after it I parse XML file, later I create new entities and save context. Earlier all worked perfect: no freezes, no crashes - all was fast on iOS 7.
But with release iOS 8 there were problems:
I resolved this problem by providing one context for all operations: deleting, creating and saving.
BUT! What I've got:
When I just install app on my device all goes well: there are no deleting, only creating entities, 7000 terms and 7 groups are parsed so fast (about 4 seconds on iPhone 6), saving goes fast too.
When I changing version of DB in my plist file (increase) my parser start this algorthm:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[Term MR_truncateAllInContext:localContext];
[Group MR_truncateAllInContext:localContext];
} completion:^(BOOL success, NSError *error) {
[self parseTermsInContext:[NSManagedObjectContext contextForCurrentThread] from:self.count];
}];
});
"saveWithBlock" method blocks Thread 1 (in profiler), my CPU loaded on 99-108(error apparently) percents (with every next update saving operation takes more and more seconds, from 20 and more, more than 120 seconds).
I've tried this way (I gathered all operations in one method for you):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
NSMutableArray *objects = [NSMutableArray arrayWithArray:[Group MR_findAllInContext:localContext]];
[objects addObjectsFromArray:[Term MR_findAllInContext:localContext]];
if (objects && [objects count] > 0) {
for (NSManagedObject *object in objects) {
[object MR_deleteInContext:localContext];
}
[localContext MR_saveToPersistentStoreAndWait];
}
[self parseTermsInContext:localContext from:self.count];
});
Here operation "MR_saveToPersistentStoreAndWait" take a long time too like "saveWithBlock".
I tried way without saving context after deleting, that is line "[localContext MR_saveToPersistentStoreAndWait];" does not exists. In this way Groups and Terms was deleted so fast too, later they was parsed so fast too but saving context was so long.
And I don't know why but even if I start deleting and saving processes in background thread saving operation froze UI thread (in UI thread I show progress from 0 to 100). When I parse XML in this thread I send message to view that one term is parsed and setting progress in percents, delegate calls method for setting progress in ProgressView in main queue.
I have not another threads that can operate core data objects.
There is link with work of app: http://rghost.ru/60274051
After 6 seconds: for test purposes I start NSTimer that updated progress every 0.3 second with fake data to fill progress for 50% before starting deleting and saving operation (updating progress goes in main queue). Timer fires several times then saving process starts in background thread but blocks main thread (as I understand) and moves setting progress operation to end (if I understand correctly).
1:08 : then after saving ends I start parsing xml-file. This is thread where I saved context after deleting. You can see progress updating. In this video it works with bugs because of a lot of manipulations, but You can believe me that it works and looks fine. After parsing 7000 objects I save context AGAIN and saving operation does not block UI thread.
Additional info:
Relations:
} completion:^(BOOL success, NSError *error) {
[self parseTermsInContext:[NSManagedObjectContext contextForCurrentThread] from:self.count];
}];
block above called in Main Thread (block UI), so contextForCurrentThread == UI Thread
instead of iterate an delete all objects you can use TruncateAll
My suggestion is:
// saveWithBlock - already perform block in background thread
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[Term MR_truncateAllInContext:localContext];
[Group MR_truncateAllInContext:localContext];
} completion:^(BOOL success, NSError *error) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// self must be weak!
[self parseTermsInContext:localContext from:self.count];
} completion:^(BOOL success, NSError *error) {
// Update UI
}];
}];

UIImagePickerController not showing (taking more time to load) camera preview in ios7

I am using GCD in my app to fetch lot of images and data in the background queue and when I present uiimagepickercontroller it takes more time to show the camera preview. After googling around, I found that many have faced this issue with iOS 7 and here is one more post that made some sense to me. (iOS 7 UIImagePickerController Camera No Image). The solution was to stop the background threads and then present the picker controller. But I am really stumped and don't know how to stop or pause the background threads, present the picker controller and then resume/start the background thread. Can someone please help me with this.
Here is how I am doing my background fetching of images and data in my networking class (like everyone else). My serial queue is initialized like this.
sharedInstance.serialQueue = dispatch_queue_create("user-detail-queue", DISPATCH_QUEUE_CONCURRENT);
And the code to fetch things in the background is like this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *myUserID = [JNKeychain loadValueForKey:#"userID"];
NSString *sessionToken = [JNKeychain loadValueForKey:#"sessionToken"];
if(sessionToken && myUserID)
{
dispatch_async([BGNetworkAPI sharedInstance].serialQueue, ^{
id object = [[BGNetworkAPI sharedInstance].userCache objectForKey:myUserID];
if(object)
{
NSDictionary *cachedResponseDictionary = (NSDictionary *)object;
BGUser *user = [BGUser createUserWithResponseDictionary:cachedResponseDictionary];
if(block)
{
block(user, nil);
}
}
else
{
[[BGUserManagementClient sharedClient] fetchUserDetailsWithUserId:myUserID withSessionToken:sessionToken withSuccessBlock:^(AFHTTPRequestOperation *operation, id responseObject)
{
//NSLog(#"The response object of my user object is %#",responseObject);
[[BGNetworkAPI sharedInstance].userCache setObject:responseObject forKey:myUserID];
NSDictionary *responseDictionary = (NSDictionary *)responseObject;
BGUser *user = [BGUser createUserWithResponseDictionary:responseDictionary];
if(block)
{
block(user,nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"The error while fetching the user details is %#", operation.responseObject);
if(block)
{
block(nil, error);
}
}];
}
}
});
With the code above you are not doing the fetching in the backgroud as dispatch_get_main_queue()gives you the queue the interface is running on. To perform a background fetch you have to open another queue with dispatch_queue_create("image_queue", NULL).
When the backgroud fetch is finished and you want to do something with the UI you have to do this in the main queue.
dispatch_async(dispatch_get_main_queue(),^{
if(block) {
block(user,nil)
}
});
After playing around with the code, I figured out the problem. In all the places where I am doing background fetch, I was sending the completion block just like that. When I changed the returning of the completion block in the main block, it started to work properly. Thanks a lot to #HackingOther to point out my mistake.

How to create multiple objects in background?

I'm using MagicalRecord 2.0.3 and I can't really figure out how to save data in the background.
According to the documentation, something like this should work:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
// Do this hundreds of times
[MyObject createInContext:localContext];
}];
However, nothing is saved to the database. I've seen multiple people posting solutions similar to this:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
// Do this hundreds of times
[MyObject createInContext:localContext];
} completion:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSManagedObjectContext defaultContext] saveNestedContexts];
}];
}];
This does save my data in the database, however since the save happens on the main thread, my application is unresponsive for a while (with my dataset, about 3 seconds which is way too long).
I've also tried this, but it also blocks up while saving:
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
// Do this hundreds of times
[MyObject createInContext:localContext];
[localContext saveNestedContexts];
}];
And lastly, same blocking effect with this code:
dispatch_queue_t syncQueue = dispatch_queue_create("Sync queue", NULL);
dispatch_async(syncQueue, ^{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
// Do this hundreds of times
[MyObject createInContext:localContext];
[[NSManagedObjectContext contextForCurrentThread] saveNestedContexts];
});
So, what is the best way to solve this? I need to create hundreds of objects in the background and the app needs to remain responsive.
MagicalRecord uses a child context when doing work in the background. This works fine for small changes, but will create excessive main thread blocking when importing large amounts of data.
The way to do it is to use a parallel NSManagedObjectContext and to do the merging yourself with the NSManagedObjectContextDidSaveNotification notification and the mergeChangesFromContextDidSaveNotification method. See performance tests here: http://floriankugler.com/blog/2013/5/11/backstage-with-nested-managed-object-contexts
When saving a nested contexts everything has to be copied to the parent context. As opposed to this, objects that have not been fetched (in the context into which you are merging) will not be merged by mergeChangesFromContextDidSaveNotification. This is what makes it faster.
You might encounter problems if you want to display these results right away after saving in batches and using an NSFetchResultsController. See the following question for a solution:
NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext
For more performance tips take a look at this question: Implementing Fast and Efficient Core Data Import on iOS 5
Create your own context.
NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[importContext setPersistentStoreCoordinator:yourPersistentStoreCoordinator];
[importContext setUndoManager:nil]; // For importing you don't need undo: Faster
// do your importing with the new importContext
// …
NSError* error = nil;
if(importContext.hasChanges) {
if(![importContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
Make sure you are listening for the saves to managed object contexts.
[[NSNotificationCenter defaultCenter]
addObserver:singleton
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification object:nil];
In the contextDidSave:you merge the change yourself.
- (void) contextDidSave:(NSNotification*) notification
{
if(![notification.object isEqual:self.mainContext]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainContext mergeChangesFromContextDidSaveNotification:notification];
});
}
}
Managed object contexts are not thread safe so if you ever need to do any kind of background work with your Coredata objects (i.e. a long running import/export function without blocking the main UI) you will want to do that on a background thread.
In these cases you will need to create a new managed object context on the background thread, iterate through your coredata operation and then notify the main context of your changes.
You can find an example of how this could work here
Core Data and threads / Grand Central Dispatch

Resources