In background queue run multiple api calls without freezing UI? - ios

NSURL Async Request gets freeze until the previous api call to complete.
Our project has a requirement to upload a video into the server, we are doing this using GCD. But, UI should not be freeze until the upload going to complete. So, there is a possible to call some other apis too. But, while calling other apis, these new api calls are waiting for the completion of video uploading api call to finish in queue.
Flow of the application is as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//api call for video upload is an NSMutableUrlRequest with Asynchronous completion handler.
dispatch_async(dispatch_get_main_queue(), ^{
//UI operations
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//simple api call for getting minimal data/ validation --- (Problem - This api is getting freeze until the video upload to complete).
});
});
});

If you don't want that innermost dispatch to wait for the completion of the video, move it outside of that dispatch block, e.g.:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//api call for video upload is an NSMutableUrlRequest with Asynchronous completion handler.
dispatch_async(dispatch_get_main_queue(), ^{
//UI operations
});
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//simple api call for getting minimal data/ validation
});
I must say, the original construct seems to suggest some implied dependency between the video and that data validation calls, but if not, you can use something like the above. If you need to know when these two separate asynchronous tasks are both done, you could use a dispatch group.

Did you take a look at AFNetworking? It uses NSURLConnection / NSURLSession for making requests and they are all asynchronous.

Related

Get Dropbox metadata in class attribute in async mode

I'm trying with the following code get metadata of Dropbox dir, calling loadMetadata Dropbox API and hope the callback (loadedMetadata) is called when data are in device. This is the code:
_semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
NSLog(#"loadMetadata() in async block");
[self.restClient loadMetadata:#"/"];
});
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
As you can see, I'm calling in async mode, to get the metadata in one property and then return in other method. I'm make the following call in loadedMetadata callback:
dispatch_semaphore_signal(_semaphore);
Good, the issue here is that loadedMetadata callback is never called. I don't know why, but the trace I put in loadedMetadata is never printed and application is freeze (waiting loadedMetadata signal). I put DISPTACH_TIME_FOREVER as example, please not what you have in mind.
From https://www.dropbox.com/developers/core/start/ios:
Make sure you call DBRestClient methods from the main thread or a
thread that has a run loop. Otherwise the delegate methods won't be
called.
You should call loadMetadata from the main thread. The actual operation is async already.

Multiple Async calls in iOS

I want to call a web service to upload some image data to the server. I have to send the data 5 times to the server. This piece of code is written in a function which is called after 10 seconds duration by a timer. Now the problem is that the response of Web service might be late and second call to web service might initiate. I want to keep them in queue so that when one finishes other is called. I think I am not going in right way. I just want to maintain a queue in which I can call the web service multiple times and make different async calls to the server. Basically how I could call multiple async tasks.
Any help will be appreciated.
dispatch_queue_t myQueue;
myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
[self uploadDataToServer];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
});
});
A simple way is to keep a counter and recurse. It looks like your uploadToServer is a blocking call so e.g.
- (void)uploadDataToServerAndRepeat:(NSUInteger)repeatCount {
if(repeatCount)
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0),
^{
[self uploadDataToServer];
[self uploadDataToServerAndRepeat:repeatCount - 1];
// dispatch async to main queue for UI update, too
});
}
// ... and, to start things off ...
[self uploadDataToServerAndRepeat:5];
Added a sample project. Maybe there was any other way for it but you can simply use TaskQueue.m class in sample process. You can modify it if you wish. https://github.com/kocakmstf/AsyncTaskQueue

iPhone App Crashes with dispatch_async if operation is not completed

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

Waiting on Multiple Async Tasks to Complete

I like to show a loading message when the app is fetching news and videos. Currently, I have the following code:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[self loadVersion];
[self loadFeaturedNews];
[self loadFeaturedVideo];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self dismissViewControllerAnimated:NO completion:nil];
});
});
I would like to dismiss the controller and hide the progress view only when all the tasks (news, videos) are loaded.
If you're using AFNetworking, you might want to look at enqueueBatchOfHTTPRequestOperations. I'd refer you to the AFNetworking FAQ:
How can I wait for a group of requests to finish before processing them?
Use [enqueueBatchOfHTTPRequestOperationsWithRequests] or [enqueueBatchOfHTTPRequestOperations] to batch a series of requests together, specifying a callback for when all of the requests have finished. As mentioned in the question about waiting for completion blocks to finish, you may not want to set completion blocks on each individual operation, and instead access the response object properties directly in the batch completion block.
I gather that loadVersion, loadFeaturedNews, and loadFeaturedVideo methods are each asynchronously downloading content. If you were using NSURLConnection, I would suggest changing them to operate synchronously. The specifics of the solution will vary depending upon how you're currently downloading stuff in those routines e.g. if you're using NSURLConnection method, initWithRequest, you could use sendSynchronousRequest.
Personally, I'd be inclined to combine that with doing the requests concurrently with dispatch groups or NSOperationQueue dependencies, but first focus on getting them to run synchronously. I'd actually use NSOperationQueue so I could easily cap the number of concurrent operations to four, or something reasonable like that.
By the way, I'm not suggesting you change the code in your question at all. Keep that dispatch_async. I'm suggesting you fix the loadVersion, loadFeaturedNews, and loadFeaturedVideo methods, themselves, to operate synchronously.
Then use completion handler for this. in that you can write your code which is to be executed after completion
completion:^ (BOOL finished) {
}

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