MagicalRecord : how to perform background saves - ios

I am building a news application, which basically fetches data from a distant server, using AFNetworkOperation (all operations are put in a NSOperationQueue in order to properly manage the synchronisation process and progress).
Each completion block of each AFNetworkOperation creates/deletes/updates core data entities.
At the whole end of the synchronisation process, in order to make all changes persistent, I perform a full save with following lines of code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
NSLog(#"saveInBackground : starting...");
[[NSManagedObjectContext defaultContext] saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
NSLog(#"saveInBackground : finished!");
}];
});
Unfortunately it always blocks the main thread during my save operation.
I might not use MagicalRecord properly and so any advice would be welcome.

After digging deeper inside MagicalRecord, it seems that my code is working well and does not block main thread at all.
My issue is not on MagicalRecord, but on the way I should use it on completion blocks of afnetworking operation.
I Will start a new discussion to provide full details on it.

Related

Switch from main queue to current NSOperation Thread

I want to download and process a image from Firebase to local directory on secondary thread and then update the UI. The problem is firebase returns in completion on main thread and not on the thread on which my NSOperation is being executed. I want to switch back to the thread on which my NSOperation is working. Is there a way I can achieve it ?
Download Meta Data from Firebase Realtime DB
Process and Store in Local Db
Update UI
Download image From Firebase to temp location
Once properly downloaded move to appropriate Directory
Update UI
Below is the sample code and have mentioned thread on which the completion is called.
[photosDetailReferenceDB observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
//Main Thread
[self.downloadQueue addOperationWithBlock:^{
//Bg Thread 2
[[[FirebaseHelper sharedManager].storageuserRef child:serverPath] writeToFile:[NSURL fileURLWithPath:tempPath] completion:^(NSURL * _Nullable URL, NSError * _Nullable error) {
//Main Thread
// Here I want to switch back to Thread 2.
// Since yet I have to move the image back to proper directory and update the image status in core data.
}];
}];
}];
It helps to stop thinking in terms of threads and start thinking in terms of queues.
Specifically, the thread of execution is, beyond the main thread, irrelevant. What is relevant, though, is the queue upon which the code is executed and whether or not that queue concurrent or serial execution.
So, in your case, the FirebaseHelper's callback to the main queue would immediately dispatch a block or operation to your background computation queue.
It may be as simple as dispatch_async() in the pure GCD queue case or, since you are using NSOperation, adding an operation to the appropriate NSOperationQueue.
If I add an operation to NSOperationQueue it would be different then
the one already executing. Firebase will be returning 100's of object
that means there would be 100's of NSOperations already and this would
lead to create another sub operation for each of them
If your operation queue is serial, then the operations will be executed one after the other, effectively as if it were the same thread (the actual thread of execution is irrelevant).
However, you don't want to have 100s or thousands of operations in flight, even in a serial queue.
You need to separate the work from the execution of the work. That is, you need to store a representation of the work that needs to be done in your data model and then pick off bits of work to be executed as your work queue or work queues (if parallelization makes sense) are emptied.

Core Data concurrency issue and how to fix

I use multiple contexts in my Core Data app, and have recently had some core data concurrency crashes. I have added -com.apple.CoreData.ConcurrencyDebug 1 to help track these down, but I am not understanding how to fix the issue that is shown.
Here is what I am doing:
- (void)getEvents:(void (^)(NSArray *fetchedItems))completionBlock {
// Initialize Fetch Request
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ZSSCDEvent"];
// Initialize Asynchronous Fetch Request
NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:request completionBlock:^(NSAsynchronousFetchResult *result) {
dispatch_async(dispatch_get_main_queue(), ^{
// Dismiss Progress HUD
[SVProgressHUD dismiss];
// Process Asynchronous Fetch Result
if (result.finalResult) {
NSArray *results = result.finalResult;
completionBlock(results);
// Reload Table View
[self.activityIndicator stopAnimating];
[self.tableViewList reloadData];
}
});
}];
// Execute Asynchronous Fetch Request
[self.managedObjectContext performBlock:^{
// Execute Asynchronous Fetch Request
NSError *asynchronousFetchRequestError = nil;
NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[self.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
if (asynchronousFetchRequestError) {
NSLog(#"Unable to execute asynchronous fetch result.");
NSLog(#"%#, %#", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
}
}];
}
This gives me a Enqueued from com.apple.main-thread (Thread 1) error. This is where I am confused, since I am running this on the main thread and didn't think I needed to use my private context here.
Any ideas on why I am getting a concurrency issue here?
EDIT: It looks like someone else had the exact issue and thinks it is an Xcode bug: CoreData asynchronous fetch causes concurrency debugger error
Every managedObject has a context. Each context has one and only one thread that it can run on. ManagedObjects are NOT thread safe - not even for reading. Passing managedObjects around with completions blocks is a bad practice. It can be hard to figure out which managedObjects are supposed to be on which thread. Also, even if you are passing it around on the correct thread it is still a bad practice. When you do a dispatch_async the entity may have deleted from the database in the interim and accessing the managedObject will cause a crash. A good practice is that any method that does a fetch should be explicitly told which context to use and return synchronously. In your code the method is using self.managedObjectContext, but I have no way to know looking at you code what thread that is related to. Furthermore the pointer to the context may change and that can cause bugs that are very hard to track down.
NSAsynchronousFetchResult contains managedObjects, so is not thread safe and can only be used inside that completion block (which is running on the correct thread for the objects). You cannot pass them to another thread. If you return them in a block then that code must also not pass them to another thread. You should just do whatever you need to do with them inside the block and then discard them.
If you need to display the information to the user, then generally it is better to just do the fetch on the main thread synchronously and get managedObjects that are associated with a main thread context. If your fetch is taking to long then you should fix that - there is no reason for a fetch to take so long. In your case you appear to be fetching ALL the items in you database. That is a mistake. You should use a predicate to only get the ones that you need.
Another possibility is to copy the values of managedObjects into a thread safe object (a dictionary or a NSObject subclass).
TL;DR You probably don't need a NSAsynchronousFetchRequest. Use a regular fetch request on the main thread and return synchronously. Use a predicate to limit the results only to the objects you are actually displaying.

Multi threading in iOS objective C

I have a doubt regarding multi threading in iOS objective C. I have never worked on threads..
In my app, I have a couple of tasks that need to run only in background so that the UI doesn't get freezed.
Following is the code snippet,
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[self someFunctionCallinWebservice];
dispatch_async(dispatch_get_main_queue(), ^(void){
//UI Updates
});
});
In the above code, function : someFunctionCallinWebservice calls webservice for which I am using AFNetworking library. In the webservice if it is a success then I am saving the data locally. I am calling a function in success block to save the data sent from server like below,
[manager POST:url parameters:parameter success:^(AFHTTPRequestOperation *operation, id responseObject){
[self functionToSaveData:someArray];
}
Here the someFunctionCallinWebservice is running in background but [self functionToSaveData:someArray] runs in foreground. Should I have this functionToSaveData also in background thread?
I mean if I am calling a function in background then all related functionalities of that function like, calling server, getting the data and saving it must also fall in background thread right? Why should I create another thread again?
Please help...
Yes, u can call functionToSaveData function in background thread it will not create any issue but if u want to do any UI updates (like :-> reload tableView, show or hide some views) at that time u must do it on main thread otherwise it will not do any effect on your UI.
dispatch_async(dispatch_get_main_queue(),^{
//Do any UI updates here
});
Edit: Swift 4
DispatchQueue.main.async {
//Do any UI updates here
}
Multi-threading is a large and difficult subject, for which iOS has different types of supports. I suggest you read Apple's Threading Programming Guide to start with.
For the type of action that you seem to be doing (fetching data from the internet), I suggest you use the iOS asynchronous APIs, such as URLSession, which remove the need to do anything with multi-threading yourself.
The answer to your concrete question depends on whether your POST:parameters:success: operation is a synchronous or an asynchronous operation, and it depends on what the functionToSaveData: actually does.
Assuming that functionToSaveData: is intended to share the data with the rest of your app, it would be best to do it on the main thread, to avoid synchronisation problems.

iOS5: Determine if core data method is running in an asynch dispatch queue?

How can I tell if a method is running asynchronously or not?
I have several core data related methods which are sometimes called asynchronously via an asynch dispatch queue (GCD) and other times are called synchronously.
// sometimes this happens
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[[DataServices sharedInstance] doSomeCoreDataStuff:^(BOOL result, NSString *message)
{
// do some post-stuff here
}];
});
// other times this happens
[[DataServices sharedInstance] doSomeCoreDataStuff:^(BOOL result, NSString *message)
{
// do some post-stuff here
}];
In addition to this, I am using a singleton NSManagedObjectContext throughout my app. Since context is not thread-safe, in the case when the core data methods are running asynchronously I need to create a new context within those methods, otherwise I want to use the singleton instance context.
The only approach that comes to mind is something like [[NSThread mainThread] isMainThread], but GCD may or may not do work on the main thread, so this will not work.
The right way to deal with this is to configure your managed object context to use one the thread confinement concurrency types-- NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType-- and then use its performBlock: method when you want to use it. That is,
[[DataServices sharedInstance] performBlock:^{
// do Core Data stuff here
}];
You can do that on any thread or queue, and it's asynchronous. There's also performBlockAndWait: if you need to get the results immediately.
You'd use that call everywhere. The only exception is if you use NSMainQueueConcurrencyType and you know that you're running on the main queue, you could make calls on the context directly instead of via performBlock:

How should I use GCD dispatch_barrier_async in iOS (seems to execute before and not after other blocks)

I'm trying to synchronize the following code in iOS5:
an object has a method which makes an HTTP request from which it
gets some data, including an URL to an image
once the data arrives, the textual data is used to populate a
CoreData model
at the same time, a second thread is dispatched async to download
the image; this thread will signal via KVO to a viewController when
the image is already cached and available in the CoreData model.
since the image download will take a while, we immediately return
the CoreData object which has all attributes but for the image to
the caller.
Also, when the second thread is done downloading, the CoreData model
can be saved.
This is the (simplified) code:
- (void)insideSomeMethod
{
[SomeHTTPRequest withCompletionHandler:
^(id retrievedData)
{
if(!retrievedData)
{
handler(nil);
}
// Populate CoreData model with retrieved Data...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURL* userImageURL = [NSURL URLWithString:[retrievedData valueForKey:#"imageURL"]];
aCoreDataNSManagedObject.profileImage = [NSData dataWithContentsOfURL:userImageURL];
});
handler(aCoreDataNSManagedObject);
[self shouldCommitChangesToModel];
}];
}
- (void)shouldCommitChangesToModel
{
dispatch_barrier_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSError *error = nil;
if(![managedObjectContext save:&error])
{
// Handle error
}
});
}
But what's going on is that the barrier-based save-block is always executed before the the image-loading block. That is,
dispatch_barrier_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSError *error = nil;
if(![managedObjectContext save:&error])
{
// Handle error
}
});
Executes before:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURL* userImageURL = [NSURL URLWithString:[retrievedData valueForKey:#"imageURL"]];
aCoreDataNSManagedObject.profileImage = [NSData dataWithContentsOfURL:userImageURL];
});
So obviously I'm not really dispatching the image-loading block before the barrier, or the barrier would wait until the image-loading block is done before executing (which was my intention).
What am I doing wrong? how do I make sure the image-loading block is enqueued before the barrier block?
At first glance the issue may be that you are dispatching the barrier block on a global concurrent queue. You can only use barrier blocks on your own custom concurrent queue. Per the GCD docs on dispatch_barrier_async, if you dispatch a block to a global queue, it will behave like a normal dispatch_async call.
Mike Ash has a good blog post on GCD barrier blocks: http://www.mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html
Good luck
T
You need to create your own queue and not dispatch to the global queues as per the ADC Docs
The queue you specify should be a concurrent queue that you create
yourself using the dispatch_queue_create function. If the queue you
pass to this function is a serial queue or one of the global
concurrent queues, this function behaves like the dispatch_async
function.
from https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_barrier_async .
You can create tons of your own GCD queues just fine. gcd queues are very small and you can create tons of them without issue. You just need to free them when you're done with them.
For what you seem to be trying to solve, dispatch_barrier_async may not be the best solution.
Have a look at the Migrating Away From Threads section of the Concurrency Programming Guide. Just using dispatch_sync on a your own serial queue may solve your synchronization problem.
Alternatively, you can use NSOperation and NSOperationQueue. Unlike GCD, NSOperation allows you to easily manage dependancies (you can do it using GCD, but it can get ugly fast).
I'm a little late to the party, but maybe next time you could try using dispatch_groups to your advantage. http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2

Resources