iPhone App Crashes with dispatch_async if operation is not completed - ios

In my shipping iPhone App, I've used a dispatch_async block without issues. The App checks a web site for price updates, parses the HTML, updates a Core Data model accordingly, and then refreshes the table being viewed.
In my latest App however, I've found I can crash the App by switching out of the App while the price update process is running. The differences between the first and second usage seem to me to be only that I'm calling the dispatch block from the table's refreshController (ie. tableViewController's now built-in pull-to-refresh mechanism) and that this is now iOS7.
Can anyone suggest to me how dispatch_async should be gracefully aborted under known conditions, such as the user wishing to stop the process, or if they switch apps like this and I'd like to intercept that activity to manage the block properly, please?
If there's any good background reading on do's and don'ts with blocks, I'd be pleased to look through such links likewise - thanks!
This is the (mostly boilerplate) dispatch_async code I'm using, for your convenience:
priceData = [[NSMutableData alloc]init]; // priceData is declared in the header
priceURL = … // the price update URL
NSURL *requestedPriceURL = [[NSURL alloc]initWithString:[#“myPriceURL.com”]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:requestedPriceURL];
dispatch_queue_t dispatchQueue = dispatch_queue_create("net.fudoshindesign.loot.priceUpdateDispatchQueue", NULL); //ie. my made-up queue name
dispatch_async(dispatchQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:urlRequest delegate:self startImmediately:YES];
[conn start];
})
});

That boilerplate code looks quite useless.
You create a serial queue. You dispatch a block on the queue which does nothing but dispatching a block on the main queue. You might as well have dispatched on the main queue directly.

Although you have a async block but you are executing NSURLConnection Request on main thread in that block, that's why app gets crashed if process doesn't get completed. Execute request in background thread. You are blocking main thread in this code.
You can do it like this:
dispatch_queue_t dispatchQueue = dispatch_queue_create("net.fudoshindesign.loot.priceUpdateDispatchQueue", 0); //ie. your made-up queue name
dispatch_async(dispatchQueue, ^{
NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:urlRequest delegate:self startImmediately:YES];
[conn start];
...
//other code
...
dispatch_async(dispatch_get_main_queue(), ^{
//process completed. update UI or other tasks requiring main thread
});
});
try reading and practicing more about GCD.
Grand Central Dispatch (GCD) Reference from Apple Docs
GCD Tutorial

There's no explicit provision in dispatch queues for cancelling. Basically, it would be a semaphore.
NSOperationQueue (a higher level abstraction, but still built using GCD underneath) has support for canceling operations. You can create a series of NSOperations and add them to an NSOperationQueue and then message cancelAllOperations to the queue when you don't need it to complete.
Useful link:
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
Dispatch queues: How to tell if they're running and how to stop them

Related

Running multiple background threads iOS

Is it possible to run multiple background threads to improve performance on iOS . Currently I am using the following code for sending lets say 50 network requests on background thread like this:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// send 50 network requests
});
EDIT:
After updating my code to something like this no performance gain was achieved :( Taken from here
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", NULL);
dispatch_group_t fetchGroup = dispatch_group_create();
// This will allow up to 8 parallel downloads.
dispatch_semaphore_t downloadSema = dispatch_semaphore_create(8);
// We start ALL our downloads in parallel throttled by the above semaphore.
for (NSURL *url in urlsArray) {
dispatch_group_async(fetchGroup, fetchQ, ^(void) {
dispatch_semaphore_wait(downloadSema, DISPATCH_TIME_FOREVER);
NSMutableURLRequest *headRequest = [NSMutableURLRequest requestWithURL:url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[headRequest setHTTPMethod: #"GET"];
[headRequest addValue: cookieString forHTTPHeaderField: #"Cookie"];
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[NSURLConnection sendAsynchronousRequest:headRequest
queue:queue // created at class init
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// do something with data or handle error
NSLog(#"request completed");
}];
dispatch_semaphore_signal(downloadSema);
});
}
// Now we wait until ALL our dispatch_group_async are finished.
dispatch_group_wait(fetchGroup, DISPATCH_TIME_FOREVER);
// Update your UI
dispatch_sync(dispatch_get_main_queue(), ^{
//[self updateUIFunction];
});
// Release resources
dispatch_release(fetchGroup);
dispatch_release(downloadSema);
dispatch_release(fetchQ);
Be careful not to confuse threads with queues
A single concurrent queue can operate across multiple threads, and GCD never guarantees which thread your tasks will run on.
The code you currently have will submit 50 network tasks to be run on a background concurrent queue, this much is true.
However, all 50 of these tasks will be executed on the same thread.
GCD basically acts like a giant thread pool, so your block (containing your 50 tasks) will be submitted to the next available thread in the pool. Therefore, if the tasks are synchronous, they will be executed serially. This means that each task will have to wait for the previous one to finish before preceding. If they are asynchronous tasks, then they will all be dispatched immediately (which begs the question of why you need to use GCD in the first place).
If you want multiple synchronous tasks to run at the same time, then you need a separate dispatch_async for each of your tasks. This way you have a block per task, and therefore they will be dispatched to multiple threads from the thread pool and therefore can run concurrently.
Although you should be careful that you don't submit too many network tasks to operate at the same time (you don't say specifically what they're doing) as it could potentially overload a server, as gnasher says.
You can easily limit the number of concurrent tasks (whether they're synchronous or asynchronous) operating at the same time using a GCD semaphore. For example, this code will limit the number of concurrent operations to 6:
long numberOfConcurrentTasks = 6;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(numberOfConcurrentTasks);
for (int i = 0; i < 50; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self doNetworkTaskWithCompletion:^{
dispatch_semaphore_signal(semaphore);
NSLog(#"network task %i done", i);
}];
});
}
Edit
The problem with your code is the line:
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", NULL);
When NULL is passed to the attr parameter, GCD creates a serial queue (it's also a lot more readable if you actually specify the queue type here). You want a concurrent queue. Therefore you want:
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", DISPATCH_QUEUE_CONCURRENT);
You need to be signalling your semaphore from within the completion handler of the request instead of at the end of the request. As it's asynchronous, the semaphore will get signalled as soon as the request is sent off, therefore queueing another network task. You want to wait for the network task to return before signalling.
[NSURLConnection sendAsynchronousRequest:headRequest
queue:queue // created at class init
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// do something with data or handle error
NSLog(#"request completed");
dispatch_semaphore_signal(downloadSema);
}];
Edit 2
I just noticed you are updating your UI using a dispatch_sync. I see no reason for it to be synchronous, as it'll just block the background thread until the main thread has updated the UI. I would use a dispatch_async to do this.
Edit 3
As CouchDeveloper points out, it is possible that the number of concurrent network requests might be being capped by the system.
The easiest solution appears to be migrating over to NSURLSession and configuring the maxConcurrentOperationCount property of the NSOperationQueue used. That way you can ditch the semaphores altogether and just dispatch all your network requests on a background queue, using a callback to update the UI on the main thread.
I am not at all familiar with NSURLSession though, I was only answering this from a GCD stand-point.
You can send multiple requests, but sending 50 requests in parallel is usually not a good idea. There is a good chance that a server confronted with 50 simultaneous request will handle the first few and return errors for the rest. It depends on the server, but using a semaphore you can easily limit the number of running requests to anything you like, say four or eight. You need to experiment with the server in question to find out what works reliably on that server and gives you the highest performance.
And there seems to be a bit of confusion around: Usually all your network requests will run asynchronously. That is you send the request to the OS (which goes very quick usually), then nothing happens for a while, then a callback method of yours is called, processing the data. Whether you send the requests from the main thread or from a background thread doesn't make much difference.
Processing the results of these requests can be time consuming. You can process the results on a background thread. You can process the results of all requests on the same serial queue, which makes it a lot easier to avoid multithreading problems. That's what I do because it's easy and even in the worst case uses one processor for intensive processing of the results, while the other processor can do UI etc.
If you use synchronous network requests (which is a bad idea), then you need to dispatch each one by itself on a background thread. If you run a loop running 50 synchronous network requests on a background thread, then the second request will wait until the first one is completely finished.

NSOperation and NSOperationQueue working thread vs main thread

I have to carry out a series of download and database write operations in my app. I am using the NSOperation and NSOperationQueue for the same.
This is application scenario:
Fetch all postcodes from a place.
For each postcode fetch all houses.
For each house fetch inhabitant details
As said, I have defined an NSOperation for each task. In first case (Task1), I am sending a request to server to fetch all postcodes. The delegate within the NSOperation will receive the data. This data is then written to database. The database operation is defined in a different class. From NSOperation class I am making a call to the write function defined in database class.
My question is whether the database write operation occur in main thread or in a background thread? As I was calling it within a NSOperation I was expecting it to run in a different thread (Not MainThread) as the NSOperation. Can someone please explain this scenario while dealing with NSOperation and NSOperationQueue.
My question is whether the database write operation occur in main
thread or in a background thread?
If you create an NSOperationQueue from scratch as in:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
It will be in a background thread:
Operation queues usually provide the threads used to run their
operations. In OS X v10.6 and later, operation queues use the
libdispatch library (also known as Grand Central Dispatch) to initiate
the execution of their operations. As a result, operations are always
executed on a separate thread, regardless of whether they are
designated as concurrent or non-concurrent operations
Unless you are using the mainQueue:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
You can also see code like this:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{
// Background work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Main thread work (UI usually)
}];
}];
And the GCD version:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
// Background work
dispatch_async(dispatch_get_main_queue(), ^(void)
{
// Main thread work (UI usually)
});
});
NSOperationQueue gives finer control with what you want to do. You can create dependencies between the two operations (download and save to database). To pass the data between one block and the other, you can assume for example, that a NSData will be coming from the server so:
__block NSData *dataFromServer = nil;
NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakDownloadOperation = downloadOperation;
[weakDownloadOperation addExecutionBlock:^{
// Download your stuff
// Finally put it on the right place:
dataFromServer = ....
}];
NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation;
[weakSaveToDataBaseOperation addExecutionBlock:^{
// Work with your NSData instance
// Save your stuff
}];
[saveToDataBaseOperation addDependency:downloadOperation];
[myQueue addOperation:saveToDataBaseOperation];
[myQueue addOperation:downloadOperation];
Edit: Why I am using __weak reference for the Operations, can be found here. But in a nutshell is to avoid retain cycles.
If you want to perform the database writing operation in the background thread you need to create a NSManagedObjectContext for that thread.
You can create the background NSManagedObjectContext in the start method of your relevant NSOperation subclass.
Check the Apple docs for Concurrency with Core Data.
You can also create an NSManagedObjectContext that executes requests in its own background thread by creating it with NSPrivateQueueConcurrencyType and performing the requests inside its performBlock: method.
From NSOperationQueue
In iOS 4 and later, operation queues use Grand Central Dispatch to execute operations. Prior to iOS 4, they create separate threads for non-concurrent operations and launch concurrent operations from the current thread.
So,
[NSOperationQueue mainQueue] // added operations execute on main thread
[NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread
In your case, you might want to create your own "database thread" by subclassing NSThread and send messages to it with performSelector:onThread:.
The execution thread of NSOperation depends on the NSOperationQueue where you added the operation. Look out this statement in your code -
[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class
All this assumes you have not done any further threading in main method of NSOperation which is the actual monster where the work instructions you have (expected to be) written.
However, in case of concurrent operations, the scenario is different. The queue may spawn a thread for each concurrent operation. Although it's not guarrantteed and it depends on system resources vs operation resource demands at that point in the system. You can control concurrency of operation queue by it's maxConcurrentOperationCount property.
EDIT -
I found your question interesting and did some analysis/logging myself. I have NSOperationQueue created on main thread like this -
self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease];
NSLog(#"Operation queue creation. current thread = %# \n main thread = %#", [NSThread currentThread], [NSThread mainThread]);
self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency
And then, I went on to create an NSOperation and added it using addOperation. In the main method of this operation when i checked for current thread,
NSLog(#"Operation obj = %#\n current thread = %# \n main thread = %#", self, [NSThread currentThread], [NSThread mainThread]);
it was not as main thread. And, found that current thread object is not main thread object.
So, custom creation of queue on main thread (with no concurrency among its operation) doesn't necessarily mean the operations will execute serially on main thread itself.
The summary from the docs is operations are always executed on a separate thread (post iOS 4 implies GCD underlying operation queues).
It's trivial to check that it is indeed running on a non-main thread:
NSLog(#"main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
When running in a thread it's trivial to use GCD/libdispatch to run something on the main thread, whether core data, user interface or other code required to run on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// this is now running on the main thread
});
If you're doing any non-trivial threading, you should use FMDatabaseQueue.

Get underlying dispatch_queue_t from NSOperationQueue

I seem to have some confusion between dispatch_queue_t and NSOperationQueue queues.
By default, AFNetworking's AFImageRequestOperation will execute the success callback block on the application's main thread. To change this, AFHTTPRequestOperation has the property successCallbackQueue which lets you choose on which queue to run the callback.
I'm trying to execute the success callback on the same background queue / background threads which already did the HTTP request. Instead of returning to the main thread, the NSOperationQueue which ran the HTTP request should run the callback as well, since there are some heavy calculations I need to do using some of the returned images.
My first try was to set successCallbackQueue to the NSOperationQueue instance on which the AFImageRequestOperation ran. However, the successCallbackQueue property is of type dispatch_queue_t, so I need a way to get the underlying dispatch_queue_t of my NSOperation instance, if there is such a thing.
Is that possible, or do I need to create a separate dispatch_queue_t?
The reason I ask: It's somewhat strange that AFNetworking inherits from NSOperation, but expects us to use dispatch_queue_t queues for the callbacks. Kind of mixing the two paradigmas dispatch_queue_t and NSOperationQueue.
Thanks for any hints!
There is no such thing, there isn't a one-to-one correspondence of an NSOperationQueue and a dispatch_queue_t, the queueing concepts in the two APIs are very different (e.g. NSOperationQueue does not have strict FIFO queueing like GCD does).
The only dispatch queue used by NSOperationQueue to execute your code is the default priority global concurrent queue.
NSOperationQueue is not your bottleneck with AFNetworking. Request operations are bound by network, not CPU or memory. All of the work is done asynchronously in dispatch queues, which are accessible as properties in AFHTTPRequestOperation.
It is not advisable to use the network thread to do any processing. This will not improve performance in any way.
Instead, if you're noticing performance issues, try limiting the maximum number of concurrent operations in the operation queue, as a way to indirectly control the amount of work being done by those background processing queues.
It is interesting that AFHTTPClient uses an NSOperationQueue to run the AFHTTPRequestOperations but GCD dispatch_queues to handle the results.
Regarding NSOperationQueue the Apple docs say:
Note: In iOS 4 and later, operation queues use Grand Central Dispatch to execute operations.
but there doesn't seem to be a public API to get the dispatch_queue for a given operation.
If it's not that important to you that the success callback has to be on exactly the same queue/thread that the original operation was executed why not set:
successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
XCode 6.4 for iOS 8.4, ARC enabled
1) "...so I need a way to get the underlying dispatch_queue_t of my NSOperation instance, if there is such a thing."
There is a property of NSOperationQueue that can help:
#property(assign) dispatch_queue_t underlyingQueue
It can be used as follows to assign to NSOperationQueue:
NSOperationQueue *concurrentQueueForServerCommunication = [[NSOperationQueue alloc] init];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
concurrentQueueForServerCommunication.underlyingQueue = concurrentQueue;
Or assign from NSOperationQueue:
NSOperationQueue *concurrentQueueForServerCommunication = [[NSOperationQueue alloc] init];
dispatch_queue_t concurrentQueue = concurrentQueueForServerCommunication.underlyingQueue;
Not sure if the API you are using for network communication updates your UI after the network task's completion, but just in case it does not, then you must know to get back onto the main queue when the completion block is executed:
dispatch_sync(dispatch_get_main_queue(), ^{
//Update your UI here...
}
Hope this helps! Cheers.
First of all, it's a good behaviour to execute the success of the AFImageRequestOperation on the main thread because the main usage of this operation is to download an image in background and display it on the UI (which should be on the main thread), but in order to satisfy the needs of those user (your case too) who want to execute the callback on other threads, there is also a successCalbackQueue.
So you can create your own dispatch_queue_t with dispatch_queue_create method or the recommended way, you should use dispatch_get_global_queue to get the main queue.
On each case, make sure that in your success block, if you are making some changes to the UI, put them inside dispatch_async(dispatch_get_main_queue(), ^{ // main op here});
Swift 3 code, based on #serge-k's answer:
// Initialize the operation queue.
let operationQueue = OperationQueue()
operationQueue.name = "com.example.myOperationQueue"
operationQueue.qualityOfService = .userInitiated
// Initialize a backing DispatchQueue so we can reuse it for network operations.
// Because no additional info is give, the dispatch queue will have the same QoS as the operation queue.
let operationQueueUnderlyingQueue = DispatchQueue(label: "com.example.underlyingQueue")
operationQueue.qualityOfService.underlyingQueue = operationQueueUnderlyingQueue
You can then use this in Alamofire (or AFNetworking) in the following manner:
Alamofire.request("https://example.com/get", parameters: nil).validate().responseJSON(queue: operationQueue.underlyingQueue) { response in
response handler code
}
A warning here, from Apple's documentation on setting the OperationQueue's underlying queue:
The value of this property should only be set if there are no operations in the queue; setting the value of this property when operationCount is not equal to 0 raises an invalidArgumentException. The value of this property must not be the value returned by dispatch_get_main_queue(). The quality-of-service level set for the underlying dispatch queue overrides any value set for the operation queue's qualityOfService property.

Using NSUrlConnection inside either NSThread or NSOperation

I am developing a static library that needs to do some stuff in the background, without interacting with the main thread. To give you an idea, think of just logging some user events. The library must keep doing this stuff until the user exits the app or sends it to the background (pushes the home button) - in other words it needs to keep doing stuff inside a loop.
The only interaction between the main app thread and the spawned thread is that occasionally the main app thread will put some stuff (an event object) into a queue that the spawned thread can read/consume. Other than that, the spawned thread just keeps going until the app exists or backgrounds.
Part of what the spawned thread needs to do (though not all of it) involves sending data to an HTTP server. I would have thought that it would be easy to subclass NSThread, override its main method, and just make a synchronous call to NSUrlConnection with some sort of timeout on that connection so the thread doesn't hang forever. For example, in Java/Android, we just subclass Thread, override the start() method and call a synchronous HTTP GET method (say from Apache's HttpClient class). This is very easy and works fine. But from what I have seen here and elsewhere, apparently on iOS it is much more complicated than this and I'm more than a bit confused as to what the best approach is that actually works.
So should I subclass NSThread and somehow use NSUrlConnection? It seems the asynchronous NSUrlConnection does not work inside NSThread because delegate methods don't get called but what about the synchronous method? Do I somehow need to use and configure the RunLoop and set up an autorelease pool? Or should I use an NSOperation? It seems to me that what I am trying to do is pretty common - does anyone have a working example of how to do this properly?
As I understand it, to use NSURLConnection asynchronously you need a runloop. Even if you use an NSOperation you still need a runloop.
All the examples I have seen use the Main Thread to start NSURLConnection which has a runloop. The examples using NSOperation are set up so the operation is Concurrent which tells NSOperationQueue not to provide it's own thread, they then make sure that NSURLConnection is started on the main thread, for example via a call to performSelectorOnMainThread:
Here is an example:
Pulse Engineering Blog: Concurrent Downloads using NSOperationQueues
You can also search the Apple documentation for QRunLoopOperation in the LinkedImageFetcher sample which is an example class showing some ins and outs of this kind of thing.
(Although I'm not sure I actually saw any code that example showing how to run your own runloop, again this example relies on the main thread.)
I've used the grand central dispatch (GCD) methods to achieve this. Here is an example that worked for me in a simple test app (I'm not sure if it applies in a static library, but may be worth a look). I'm using ARC.
In the example, I am kicking off some background work from my viewDidLoad method, but you can kick it off from anywhere. The key is that "dispatch_async(dispatch_get_global_queue…" runs the block in a background thread. See this answer for a good explanation of that method: https://stackoverflow.com/a/12693409/215821
Here is my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[self doStuffInBackground];
});
}
The doStuffInBackground method is running in the background at this point, so you can just use NSURLConnection synchronously. In my example here, the method loops making network calls until presumably some other code sets backgroundStuffShouldRun = false. A network call is made with a 10 second timeout. After the call, I'm updating a UI label just to show progress. Note that the UI update is performed with "dispatch_async(dispatch_get_main_queue()…". This runs the UI update on the UI thread, as required.
One potential issue with this background work: there isn't a way to cancel the http request itself. But, with a 10 second timeout, you'd be waiting a max of 10 seconds for the thread to abort itself after an outsider (likely some event in your UI) sets backgroundStuffShouldRun = false.
- (void)doStuffInBackground
{
while (backgroundStuffShouldRun) {
// prepare for network call...
NSURL* url = [[NSURL alloc] initWithString:#"http://maps.google.com/maps/geo"];
// set a 10 second timeout on the request
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:10];
NSError* error = nil;
NSURLResponse *response = nil;
// make the request
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// were we asked to stop the background processing?
if (!backgroundStuffShouldRun) {
return;
}
// process response...
NSString* status = #"Success";
if (error) {
if (error.code == NSURLErrorTimedOut) {
// handle timeout...
status = #"Timed out";
}
else {
// handle other errors...
status = #"Other error";
}
}
else {
// success, handle the response body
NSString *dataAsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", dataAsString);
}
// update the UI with our status
dispatch_async(dispatch_get_main_queue(), ^{
[statusLabel setText:[NSString stringWithFormat:#"completed network call %d, status = %#", callCount, status]];
});
callCount++;
sleep(1); // 1 second breather. not necessary, but good idea for testing
}
}

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