GCD and async NSURLConnection - objective-c

I know that if I create an NSURLConnection (standard async one), it will call back on the same thread. Currently this is on my main thread. (work fine too).
But i'm now using the same code for something else, and I need to keep my UI snappy....
If i do
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* and inside here, at some NSURLConnection is created */
});
.. is it possible that my NSURLConnection is created but my thread disappears before the url connection has returned?
I'm new to GCD. How would one keep the thread alive until my url connection returned, or is there a better way I could be doing this?

So really the issue isn't the lifetime of the thread on which your block runs, it's the fact that this particular thread is not going to have a runloop configured and running to receive any of the events coming back from the connection.
So how do you solve this? There are different options to think about. I can list a few, and I'm sure others will list more.
1 - You could use a synchronous connection here. One disadvantage is that you won't get callbacks for authentication, redirection, caching, etc. (All the normal disadvantages of synchronous connections.) Plus each connection will of course block a thread for some period of time, so if you're doing a lot of these then you could potentially have a few threads blocked at once, which is expensive.
2 - If your connection is simple and you are using iOS5 then you can use this method:
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
queue:(NSOperationQueue*) queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))
This will start an asynchronous connection and then allow you to specify a completion handler (for success or failure) and a NSOperationQueue on which you want that block to be scheduled.
Again, you have the disadvantages of not getting the callbacks you might need for authentication, caching, etc. But at least you don't have threads hanging around blocked by connections that are in flight.
3 - Another option for iOS5 is to set the queue for all delegate callbacks:
- (void)setDelegateQueue:(NSOperationQueue*) queue NS_AVAILABLE(10_7, 5_0);
If you use this, then all of the delegate methods will be executed in the context of whatever NSOperationQueue you specify. So this is similar to option #2, expect that you get all of the delegate methods now to handle authentication, redirection, etc.
4 - You could set up your own thread that you control specifically for managing these connections. And in setting up that thread, you configure a runloop appropriately. This would work fine in iOS4 and 5 and obviously gives you all of the delegate callbacks that you want to handle
5 - You might think about what parts of your asynchronous connection handling are really interfering with your UI. Typically kicking off the connection or receiving delegate callbacks are not that expensive. The expensive (or indeterminate) cost is often in the processing of the data that you collect at the end. The question to ask here is are you really saving time by scheduling a block on some queue just to start an asynchronous connection that will go off immediately and do its thing on another thread anyway?
So you could just start the connection from the main thread, and receive all of the delegate callbacks on the main thread, and then in your implementation of those delegate methods fire off whatever expensive work you need to do on some other queue or thread.
So something like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// go ahead and receive this message on the main thread
// but then turn around and fire off a block to do the real expensive work
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Parse the data we've been collecting
});
}
Again, this is not comprehensive. There are many ways to handle this, depending on your specific needs here. But I hope these thoughts help.

Just as an answer to why your thread was disppearing (and for future reference) the NSURLConnection needs a runloop. If you had added
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
You'd see that the connection runs properly and the thread doesn't disappear untill the connection was completed.

First off, your block and every variable you use within it will get copied to GCD, so the code will not be executed on your thread but on the global queue.
If you want to get your data back on the main thread, you can nest an async call after your data has been fetched:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"www.stackoverflow.com"]];
NSURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
// handle error
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// do something with the data
});
});
But why not use NSURLConnection's built in asynchronous support? You need an NSOperationQueue, but if you are doing alot of network fetches it is the way to go anyway:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"www.stackoverflow.com"]];
[NSURLConnection sendAsynchronousRequest:request
queue:self.queue // created at class init
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// do something with data or handle error
}];
Personally, I use a library like AFNetworking or ASIHTTPRequest to make networking even easier, which both support blocks (the former utilizes GCD and is a bit more modern).

dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[btnCreateSmartList setEnabled:NO];
[dbSingleton() createEditableCopyOfDatabaseIfNeeded];
[dbSingleton() insert_SMART_PlaceList:txtListName.text :0:txtTravelType.text: [strgDuration intValue]:strgTemprature:Strgender:bimgdt];
[self Save_items];
//*********navigate new
dispatch_async(dispatch_get_main_queue(), ^{
[activityIndicator stopAnimating];
[self performSelector:#selector(gonext_screen) withObject:nil afterDelay:0.0];
});
});

Related

NSOperationQueue INSIDE an NSOperation

I created an NSOperation which goal is to download a few images (like 20) from 20 URLs.
So inside this NSOperation I create 20 AFImageRequestOperation add them in an NSOperationQueue and call -waitUntilAllOperationsAreFinished on the queue.
Problem is, it doesn't wait, it returns instantly. Here is the code
- (void)main {
NSArray *array = [I have the 20 links stored in this array];
self.queue = [[NSOperationQueue alloc]init];
self.queue.maxConcurrentOperationCount = 1;
for (int i = 0; i < array.count; i++) {
NSURL *url = [NSURL URLWithString:[array objectAtIndex:i]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFImageRequestOperation *op = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:^UIImage *(UIImage *image) {
return image;
} success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// SUCCESS BLOCK (not relevant)
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
// FAILURE BLOCK (not relevant)
}];
[self.queue addOperation:op];
}
[self.queue waitUntilAllOperationsAreFinished]; // Here is the problem it doesn't wait
DDLogWarn(#"-- %# FINISHED --", self);
}
In the console, the DDLogWarn(#"-- %# FINISHED --", self); prints before every operations even started, so my guess was that the waitUntilAllOperationsAreFinished didn't do its job and didn't wait, but the 20 Operations are still running after that which may means that the main didn't return yet, so I don't know what to think anymore.
EDIT 1 : Actually I'm wondering, if my DDLog inside the success and failure blocks because waitUntilAllOperationsAreFinished is blocking the thread until all operations complete. That may explain why I don't see anything in happening and everything suddenly.
I have found it useful to create an NSOperation that contains other NSOperations for similar reasons to you, i.e. I have a lot of smaller tasks that make up a bigger task and I would like to treat the bigger task as a single unit and be informed when it has completed. I also need to serialise the running of the bigger tasks, so only one runs at a time, but when each big task runs it can perform multiple concurrent operations within itself.
It seemed to me, like you, that creating an NSOperation to manage the big task was a good way to go, plus I hadn't read anything in the documentation that says not to do this.
It looks like your code may be working after all so you could continue to use an NSOperation.
Depending on your circumstances blocking the thread may be reasonable. If blocking isn't reasonable but you wanted to continue using an NSOperation you would need to create a "Concurrent" NSOperation see Concurrency Programming Guide: Configuring Operations for Concurrent Execution
If you only allow one image download at a time, you could use #jackslashs suggestion to signal the end of the operation, or if you want to allow concurrent image downloads then you could use a single NSBlockOperation as the final operation and use -[NSOperation addDependency:] to make it dependant on all the other operations so it would run last.
When you get the signal that everything is finished and you can set the isFinished and isExecuting flags appropriately as described in the documentation to finalise your main NSOperation.
Admittedly this has some level of complexity, but you may find it useful because once that complexity is hidden inside an NSOperation the code outside may be simpler as was the case for me.
If you do decide to create a Concurrent NSOperation you may find the Apple sample code LinkedImageFetcher : QRunLoopOperation useful as a starting point.
This code doesn't need to be inside an NSOperation. Instead make a class, perhaps a singleton, that has an operation queue and make a method called
-(void)getImagesFromArray:(NSArray *)array
or something like that and your code above will work fine enqueueing onto that queue. You don't need to call waitUntilAllOperationsAreFinished. Thats a blocking call. If your queue has a max operation count of 1 you can just add another operation to it once you have added all the network operations and then when it executes you know all the others have finished. You could just add a simple block operation on to the end:
//add all the network operations in a loop
[self.operationQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
//this is the last operation in the queue. Therefore all other network operations have finished
}]];

Asynchronous network operations in iOS 5

Everywhere I look, I see people explicitly using queues or run loops to do network operations. Should I still do that in iOS 5, or should I use NSURLConnection sendAsynchronousRequest:queue:completionHandler: instead? Is this the preferred method of doing network operations in iOS >= 5?
I can't answer for others' preference, but I rolled my own < os5, and I strongly prefer the block operation. a) I'm never interested in intermediate results of the network operation, or the repetitive code to handle them, b) the block is retained, so I get fewer race conditions where some aspect of the delegate gets prematurely released, and c) I never get mixed up about what code is running when a particular operation finishes.
In short, it's a huge improvement in the NSURLConnection interface, IMO.
It depends. For simple things, the new block-based API makes life a lot easier. However, compared to implementing the NSURLConnectionDelegate methods, you lose some control.
For example, it's not possible to cancel connections that have been initiated with this method, or to handle the response (e.g. for parsing headers) before all data is downloaded.
You can do something similar to this with iOS 4 as well using GCD.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSData *data = [NSURLConnection sendSynchronousRequest:blah returningResponse:response error:blah];
//process response body here
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI Code here
self.textView.text = [[NSString alloc] initWithData:data];
});
});
The problem with this code and the code OP posted is that, once the connection is made, you cannot cancel them.
Canceling a request on viewDidDisappear goes a long way in improving the performance of your application. I talk extensively about this on the book iOS PTL
Second reason why you need a third party networking framework like MKNetworkKit or RestKit or the now defunct ASIHTTP is for authentication. Most web service require that you authenticate using a NSURLCredential (HTTP Basic or HTTP Digest or Windows NTLM or oAuth)
This alone will take a couple of days to do if you roll out your own code. Not that you shouldn't do it. But there is no need for one as all these third party frameworks are extensively used and the chances or bugs or performance issues in them is less compared to your own code.
I've written a blog post that compares several approaches including NSURLConnection, AFNetworking, and ReactiveCocoa.
ReactiveCocoa approach
If you want to get really fancy with asynchronous network calls, you can try out ReactiveCocoa. ReactiveCocoa allows you to define signals that represent each of your network requests. And it allows you to chain multiple dependent network calls.
#import <ReactiveCocoa/ReactiveCocoa.h>
// ...
-(RACSignal*)signalGetNetworkStep1 {
NSURL* url = ...;
return [RACSignal createSignal:^(RACDisposable *(id subscriber) {
NSURLRequest *request1 = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation1 = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[subscriber sendNext:JSON];
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
[subscriber sendError:error];
}];
[operation1 start];
return nil;
}
}
-(void) yourMethod {
[[self signalGetNetworkStep1] subscribeNext:^(id *x) {
// perform your custom business logic
}];
}

GCD dispatch_async and NSURLConnection

I wanted a quick and easy way to get data from a URL without having to mess with delegates.
Is there anything wrong with the following?
// Use gcd
dispatch_queue_t queue = dispatch_queue_create("com.dowork", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue, ^{
// Do work in the background
NSData *response = [NSURLConnection sendSynchronousRequest:serviceRequest returningResponse:&serviceResponse error:&serviceError];
dispatch_async(main, ^{
// Update UI
self.data = response;
[self.tableView reloadData];
});//end
});//end
I thought I read somewhere long ago that using the NSURLConnection synchronous method in a background thread would cause memory leaks. Is this true?
Are there any issues with the codes that is posted there? Any issues with assigning the data to self.data within the block?
If you are targeting ios5 and later, there's NSURLConnection's sendAsynchronousRequest:queue:completionHandler:
To answer your specific question, it looks to me like response might leak: I don't know if there is an implicit autorelease pool on GCD threads.
Done some research now: GCD threads have their own autorelease pools but you don't know when they will be drained. You probably want to bracket the first two statements with an explicit autorelease pool.
See also Do you need to create an NSAutoreleasePool within a block in GCD?

UITableView Refresh Data

I have a UITableViewController that when opened displays a table of the following object:
class {
NSString *stringVal;
int value;
}
However, whenever this controller opens, I want it to download the data from the internet and display "Connecting..." in the status bar and refresh the stringVal and value of all of the objects. I do this by refreshing the array in the UITableViewController. However, to do this the UI hangs sometimes or even displays "blank" table cells until the operation has ended. I'm doing this in an NSOperationQueue to download the data, but I'm wondering if there's a better way to refresh the data without those weird UI bugs.
EDIT:
the UI no longer displays blank cells. This was because cellForRowAtIndexPath was setting nil values for my cellText. However, it still seems somewhat laggy when tableView.reloadData is called even though I'm using NSOperationQueue.
EDIT2:
Moreover, I have two problems: 1. the scrolling prevents the UI from being updated and 2. when the scrolling does stop and the UI starts to update, it hangs a little bit. A perfect example of what I'm trying to do can be found in the native Mail app when you view a list of folders with their unread count. If you constantly scroll the tableview, the folders unread count will be updated without any hanging at all.
Based on your response in the question comments, it sounds like you are calling [tableView reloadData] from a background thread.
Do not do this. UIKit methods, unless otherwise specified, always need to be called from the main thread. Failing to do so can cause no end of problems, and you are probably seeing one of them.
EDIT: I misread your comment. It sounds like you are not updating the UI from a background thread. But my comments about the architecture (i.e. why are you updating in a background thread AFTER the download has finished?).
You state that "when the data comes back from the server, I call a background operation..." This sounds backwards. Normally you would have your NSURLConnection (or whatever you are using for the download) run on the background thread so as not to block to UI, then call out to the main thread to update the data model and refresh the UI. Alternatively, use an asynchronous NSURLConnection (which manages its own background thread/queue), e.g.:
[NSURLConnection sendAsynchronousRequest:(NSURLRequest *)
requestqueue:(NSOperationQueue *)queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler];
And just make sure to use [NSOperationQueue mainQueue] for the queue.
You can also use GCD, i.e., nested dispatch_async() calls (the outer to a background queue for handling a synchronous connection, the inner on the main queue to handle the connection response).
Finally, I will note that you in principle can update your data model on the background thread and just refresh the UI from the main thread. But this means that you need to take care to make your model code thread-safe, which you are likely to mess up at least a couple times. Since updating the model is probably not a time consuming step, I would just do it on the main thread too.
EDIT:
I am adding an example of how one might use GCD and synchronous requests to accomplish this. Clearly there are many ways to accomplish non-blocking URL requests, and I do not assert that this is the best one. It does, in my opinion, have the virtue of keeping all the code for processing a request in one place, making it easier to read.
The code has plenty of rough edges. For example, creating a custom dispatch queue is not generally necessary. It blindly assumes UTF-8 encoding of the returned web page. And none of the content (save the HTTP error description) is localized. But it does demonstrate how to run non-blocking requests and detect errors (both at the network and HTTP layers). Hope this is helpful.
NSURL *url = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_queue_t netQueue = dispatch_queue_create("com.mycompany.netqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(netQueue,
^{
// We are on a background thread, so we won't block UI events (or, generally, the main run loop)
NSHTTPURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
dispatch_async(dispatch_get_main_queue(),
^{
// We are now back on the main thread
UIAlertView *alertView = [[UIAlertView alloc] init];
[alertView addButtonWithTitle:#"OK"];
if (data) {
if ([response statusCode] == 200) {
NSMutableString *body = [[NSMutableString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
[alertView setTitle:#"Success"];
[alertView setMessage:body];
}
else {
[alertView setTitle:#"HTTP Error"];
NSString *status = [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]];
[alertView setMessage:status];
}
}
else {
[alertView setTitle:#"Error"];
[alertView setMessage:#"Unable to load URL"];
}
[alertView show];
[alertView release];
});
});
dispatch_release(netQueue);
EDIT:
Oh, one more big rough edge. The above code assumes that any HTTP status code != 200 is an error. This is not necessarily the case, but handling this is beyond the scope of this question.

Problems Getting Multiple NSURLConnections to Run in Parallel

I am am trying to get multiple NSURLConnections to run in parallel (synchronously), however if it is not running on the main thread (block of code commented out below) the URL connection doesn't seem to work at all (none of the NSURLConnection delegate methods are triggered). Here is the code I have (implementation file of an NSOperation subclass):
- (void)start
{
NSLog(#"DataRetriever.m start");
if ([self.DRDelegate respondsToSelector:#selector(dataRetrieverBeganExecuting:)])
[self.DRDelegate dataRetrieverBeganExecuting:identifier];
if ([self isCancelled]) {
[self finish];
} else {
/*
//If this block is not commented out NSURLConnection works, but not otherwise
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}*/
SJLog(#"operation for <%#> started.", _url);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSURLRequest * request = [NSURLRequest requestWithURL:_url];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
} //not cancelled
}//start
Ran through it with a debugger, and after the end of this start method none of the NSURLConnection delegates trigger (I set breakpoints there). But on the main thread it works just fine. Any ideas of what's up? Thanks!
Background threads don't automatically have an active run loop on them. You need to start up the run loop after you create the NSURLConnection in order to get any input from it. Fortunately, this is quite simple:
[[NSRunLoop currentRunLoop] run];
When you say that you are running the connections synchronously, you are incorrect. The default mode of NSURLConnection is asynchronous -- it creates and manages a new background thread for you, and calls back to the delegate on the original thread. You therefore don't need to worry about blocking the main thread.
If you do actually want to perform a synchronous connection, you would use sendSynchronousRequest:returningResponse:error:, which will directly return the data. See "Downloading Data Synchronously" for details.
NSURLConnection needs an active run loop to actually work; the easiest way to ensure this is to just run it from the main thread.
Note that NSURLConnection normally runs asynchronously (and if you run one synchronously, what it really does is run one asynchronously on another thread and then block until that completes), so except for whatever processing you do in your delegate methods it shouldn't have much of an effect on UI responsiveness.