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
}];
}
Related
I'm new to objective-c and I'm building an app that requires a background polling to a generic API to refresh some data on my user interface.
After several hours looking for an answer/example that fits my problem I came across some solutions like the following:
long polling in objective-C
polling an external server from an app when it is launched
Poll to TCP server every hour ios
http://blog.sortedbits.com/async-downloading-of-data/
but unfortunately none of them covers my scenario which is really basic:
I need to start a polling when viewDidLoad, let's say an infinite loop, and on every iteration, let's say every 10 seconds, to call an API and when I didReceiveData I want to log that data into the console with an NSLog, obviously this can't be done on the main thread.
What I really need is a very simple example on how to do that, and with simple I mean:
I can't implement long polling/push notifications for many reasons and not looking for a way to do it, lets' just assume I cannot.
I don't want to rely on fancy frameworks like LRResty, RESTKit, AFNetworking or anything else since I don't really need them and also I can't believe that there is no SDK bulletin that can cover this basic scenario.
I don't need anything else besides what I described, so no authentication, no parameters in my request, no response handling etc. (since I'm new to objective-c and stuff not strictly needed could just be more confusing to me...)
The solution I'm looking for could be something like this (using NSOperationQueue to run my loop into a separate thread):
- (void)viewDidLoad {
[super viewDidLoad];
//To run polling on a separate thread
operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(doPolling) object:nil];
[operationQueue addOperation:operation];
}
-(void)doPolling {
MyDao *myDao = [MyDao new];
while (true) {
[myDao callApi];
[NSThread sleepForTimeInterval:10];
}
}
// and in MyDao
-(void)callApi {
NSMutableString *url= [NSMutableString stringWithFormat:#"%#",#"http:www.example.it"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
request.HTTPMethod = #"GET";
self.conn= [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(self.conn){
[self.conn start];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData %s","yes");
}
but unfortunately as stated here: Asynchronous NSURLConnection with NSOperation looks like I cannot do that.
Please help, I refuse to believe that there isn't a simple straightforward solution for this basic scenario.
I am performing a simple get request with Apple's NSURLSession#dataTaskWithURL method
[[mySession dataTaskWithURL:myUrl completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// fancy code goes here...
}] resume];
If I want to have a condition to do something IF the status code was a success, it appears that I have to cast the NSURLResponse as a NSHTTPURLResponse.....
NSUInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (!error && statusCode == 200) {
// even fancier code goes here
} else {
// omg!!!!!!!!!
}
...
But---------- My question is: WHY!?!?!?!?!?!?!?
Why in the world would Apple pass in a response object casted to a type that doesn't know its own status?! The fact that I had to write this code makes me think I am doing something very wrong, and there has to be a better way to know whether it was a 200, 404, or 500...
I was hoping I could just change the completionHandler's block argument types to be a NSHTTPURLResponse, however that appears to be a no go!
Protocol design, if done well, always has an eye on the future.
Though we're only using NSURLSession for HTTP right now, it might be possible in the future to use it for other networking protocols. It wouldn't surprise me if you could use it for FTP at some point, for example.
Using a simple response base class for the completion block/delegate parameter and letting you cast based on what kind of network protocol you know you're using gives NSURLSession flexibility for the future.
If a new networking protocol emerges with different needs—perhaps it doesn't even have an integer status code in its response, say—Apple can just add another subclass of NSURLResponse, and leave everything else the same, including the signature of the completion handler block and all the NSURLSession*Delegate protocols.
If they'd "hard coded" the network protocols and completion handler blocks to use NSHTTPURLResponse, then how would you cleanly add a new network protocol, say FTP, to NSURLSession?
(NB: By "protocol", as opposed to "networking protocol"—I use both terms in this answer—I mean the design of the interfaces of the NSURLSession classes, including their actual protocols and equivalent completion handlers.)
Typecast an instance of NSHTTPURLResponse from the response and use its statusCode method.
[NSURLConnection sendAsynchronousRequest:myRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSLog(#"response status code: %ld", (long)[httpResponse statusCode]);
// do stuff
}];
I have several "proxy" classes, which inherit from a "base proxy". These classes connect to my server and pass data to their delegates. In event of 0 status code, I want to handle these different requests in the same way.
For 0 status codes, I want to retry the method in 5 seconds, hoping the user's internet connection has improved.
SomethingProxy.m
- (void)fetchSomething {
NSString *fullPath = [NSString stringWithFormat:#"%#/route/index.json",MY_BASE_URL];
NSURL *url = [NSURL URLWithString:fullPath];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *d = (NSDictionary *)responseObject;
[self.delegate fetchedPolicy:d[#"keypath"]];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleOperationFailed:operation action:^{
[self fetchSomething];
}];
}];
NSOperationQueue *q = [[NSOperationQueue alloc] init]; [q addOperation:operation];
}
MyBaseProxy.m
- (bool)shouldRetryOperation:(AFHTTPRequestOperation *)o {
return self.retries < [self maxRetries];
}
- (void)handleOperationFailed:(AFHTTPRequestOperation *)o action:(ActionBlock)block {
NSInteger statusCode = o.response.statusCode;
if (statusCode == 0) {
if ([self shouldRetryOperation:o]) {
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
self.retries++;
block();
});
} else {
self.retries = 0;
[SVProgressHUD showErrorWithStatus:#"Please check your internet connection and try again"];
return;
}
}
self.retries = 0;
What's a better way to handle the request failure? Should I subclass AFHTTPRequestOperation?
EDIT: Removed confusing text. When I meant "same way", I meant per request Eg. handle all 500s the same, handle all 403s the same. I'm specifically asking for handling status code 0 - no internet connection.
The key situation you should be concerned with, IMHO, is when the network, itself, is not available to the device. In that case, you'd generally use Reachability to have a notification posted to your app when the network becomes available. No point in repeatedly retrying when (a) you know the network is unavailable; and (b) you easily can be notified when the network becomes available again.
If you're concerned about server-specific issues, I would be careful about send a request in 5 seconds. Let's say your server is overwhelmed and cannot respond to all the client requests. Having large numbers of clients proceeding to retry every 5 seconds might not improve the situation. It sort of depends upon why your server is not responding. You just want to make sure that the attempts to connect again will never make the situation worse.
(As an aside, the description for 403 says, "The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated.")
If you're going to employ a retry process, I'd specify some reasonable maximum number of retries (which, judging by your revised answer, you are doing). And perhaps you'd want something that when the app returns to foreground, you reinitiate your "retry three times, once every 15 seconds" logic (or whatever you settle upon). The scenario I'm thinking about is "user started app, it failed after exceeding max retries, and hit the home button on their device, and a few minutes/hours later, they tap on the app again (which might still be running)."
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
}]];
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];
});
});