How do you get the status code from dataTaskWithURL's NSURLResponse? - objective-c

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
}];

Related

Error Handling Objective C during no response

In my app I use following methods to POST/GET data from a remote server.
postData = [self sendSynchronousRequest:request returningResponse:&response error:&error];
- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
NSError __block *err = NULL;
NSData __block *data;
BOOL __block reqProcessed = false;
NSURLResponse __block *resp;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) {
resp = _response;
err = _error;
data = _data;
reqProcessed = true;
}] resume];
while (!reqProcessed) {
[NSThread sleepForTimeInterval:0];
}
*response = resp;
*error = err;
return data;
}
I have basic error handling for no network connectivity, and app directs users to no network connectivity viewController. But I would like to account for situations when for instance server is down or data format of api has changed, Just wondering how would I catch such errors and prevent the app from crashing.
A couple of general points:
Don't do requests synchronously. Pass in a block with code that should run when the request completes. There's no reason to tie up a thread waiting for a completion handler to run, and it is even worse if that thread is basically continuously running as yours is.
If you absolutely must do that (why?), at least use a semaphore and wait on the semaphore. That way, you aren't burning a CPU core continuously while you wait.
The way you avoid crashing is to sanity check the data you get back, e.g.
Check the length of the data and make sure you didn't get nil/empty response data.
Assuming the server sends a JSON response, wrap your NSJSONSerialization parsing in an #try block, and if it fails or throws an exception, handle the error in some useful way.
Generally assume that all data is invalid until proven otherwise. Check to make sure the data makes sense.
If you're in charge of the server side, consider passing an API version as an argument, and as you modify things in the future, make sure that incompatible changes occur only when responding to clients that request a newer API version (or, alternatively, send a response that tells the client that it needs to be upgraded, and then refuse to provide data).

NSURLConnection status code for Asynchronous request iOS 6

Currently using sendAsynchronous from NSURLConnection to make post and get requests, but can't get the status code in the response. Most post suggest the use of NSURLConnection and its delegate methods, which I understand are also Asynchronous.
I don't understand how the delegates send information back to the calling method (the method that eventually needs the data). The sendAsynchronous method has a call back that I am using right now.
I'm new to this, thank you for your help.
As you are using sendAsynchronousRequest:queue:completionHandle method, I will try to answer in that context:
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error) {
// This will get the NSURLResponse into NSHTTPURLResponse format
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
// This will Fetch the status code from NSHTTPURLResponse object
int responseStatusCode = [httpResponse statusCode];
//Just to make sure, it works or not
NSLog(#"Status Code :: %d", responseStatusCode);
}];
I have a wrapper class that makes this very easy and keeps your code clean:
https://github.com/ricardocontrerasrobles/EasyNetwork

Using objective-c to get coldfusion generated JSON

By going to a url test.com/test.cfm I am able to output the following:
{"COLUMNS":["OBJID","USERNAME","USERPASSWORD","USERFNAME","USERMI","USERLNAME","COMPANY","TBLCOMPANYID","TBLTITLE","USERADDRESS1","USERADDRESS2","USERCITY","USERSTATE","USERZIP","USERSIGNATUREFILE","USERBUSINESSNUMBER","USERBUSINESSEXT","USERFAXNUMBER","USERCELLNUMBER","USEROTHERNUMBER","USEREMAIL1","USEREMAIL2","USEREMAIL3","DEFAULTPROJECTID","SIGNATURE","SIGNATUREUPLOADBY","SORTORDER","DISABLESTATUS","UUID","SITEID","PROGRAMID"],
"DATA":[[1,"test",11214.0,"admin","","admin","adf Inc.",1,1,"admin","","","California","","","",null,"","","","admin#test.com","","",0,null,null,0,false,"468373c5-1234-1234-1234-3133a2bb1679",62,1]]}
To iterate through this I will first need to get the data using this?
NSMutableData *receivedData;
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"test.com/test.cfm"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:receivedData];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
NSLog(#"test = %#",receivedData);
} else {
// Inform the user that the connection failed.
}
Am I on the right track? The output says null...
You've not assigned anything to receivedData; it'll either be nil (under ARC) or an undefined value which may or may not be nil (under non-ARC). You've created an object that could be used to initiate a URL connection but done nothing with it. You're also probably not getting a valid NSURL because you've failed to specify the URI scheme (ie, the http://).
Probably the easiest thing to do (assuming at least iOS 5 and/or OS X v10.7) would be to use NSURLConnection's +sendAsynchronousRequest:queue:completionHandler: and then NSJSONSerialization to parse the result. E.g.
NSURLRequest *theRequest =
[NSURLRequest
requestWithURL:[NSURL URLWithString:#"http://test.com/test.cfm"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// or just use [NSURLRequest requestWithURL:[NSURL ...]], since the
// protocol cache policy and a 60s timeout are the default values anyway
[NSURLConnection
sendAsynchronousRequest:theRequest
queue:[NSOperationQueue mainQueue]
completionHandler:
^(NSHTTPURLResponse *urlResponse, NSData *data, NSError *error)
{
// catch all for connection errors...
if(
(urlResponse.statusCode < 200) ||
(urlResponse.statusCode >= 300) || // bad status code, e.g. 404
error || // error is non-nil would imply some other error
![data length] // data returned was empty
)
{
// report some sort of connection error
return;
}
NSError *jsonError = nil;
id <NSObject> returnedJSONObject =
[NSJSONSerialization
JSONObjectWithData:data options:0 error:&jsonError];
if(jsonError)
{
// server returned unintelligible JSON...
return;
}
NSLog(#"Got object %#", returnedJSONObject);
// then, e.g.
if(![jsonObject isKindOfClass:[NSDictionary class]])
{
// server didn't return a dictionary
return;
}
NSArray *columns = [jsonObject objectForKey:#"COLUMNS"];
if(![columns isKindOfClass:[NSArray class]])
{
// server returned no COLUMNS object, or that
// object wasn't an array
return;
}
NSLog(#"columns are %#", columns);
/* etc, etc */
}];
The class type checking stuff quickly gets quite tedious if you don't find a way to automate it but that's all validation stuff not directly related to your question.
What the above achieves is that it sends an asynchronous (ie, non-blocking) request for the contents of the URL. Results are accumulated on the main queue (ie, the same place you'd normally do user interactions). Once that entire HTTP operation is complete the code you specified at the bottom is called and that validates and parses the response. It does so synchronously so will block but that's not worth worrying about unless profiling shows it's worth worrying about.
The built-in parser is used for the JSON and everything down to 'Got object' is really just ensuring that the fetch and parse succeeded. It may not be complete — if you can think of anything else that might go wrong then don't assume I've ignored it on purpose.
At that point you just have an object of unknown type but it'll normally be a dictionary or an array because of the fundamentals of JSON. So the example code tests that it really is a dictionary then uses the normal NSDictionary interface to obtain an object for the 'COLUMNS' key. If you attempted to call objectForKey: on an array you'd raise an exception since arrays don't implement that method.
It's then fairly rote — the code checks that the object stored as 'COLUMNS' was an array. Per the JSON rules it could have been another dictionary or a string or one of a few other things. Possibly of interest is that the code calls isKindOfClass: to test that an object was found and that it was an array in a single call; that works because it's explicitly permissible to send any message to nil and the result will always be nil, which looks the same as a BOOL NO.

NSURLConnection sendAsynchronousRequest:queue:completionHandler not working in iOS 4.3

I am using [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) in my app. By using this my app is terminated in iOS 4.3 but it is working fine in iOS 5.0.
How to use this in iOS 4.3 can any one help me.
Here's a full implementation that works for me. Feel free to rename it and add as a category on NSURLConnection, or just add it as a local method in the class you're working in.
-(void)sendAsynchronousRequest:(NSURLRequest*)request queue:(NSOperationQueue*)queue completionHandler:(void(^)(NSURLResponse *response, NSData *data, NSError *error))handler
{
__block NSURLResponse *response = nil;
__block NSError *error = nil;
__block NSData *data = nil;
// Wrap up synchronous request within a block operation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
}];
// Set completion block
// EDIT: Set completion block, perform on main thread for safety
blockOperation.completionBlock = ^{
// Perform completion on main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
handler(response, data, error);
}];
};
// (or execute completion block on background thread)
// blockOperation.completionBlock = ^{ handler(response, data, error); };
// Execute operation
[queue addOperation:blockOperation];
}
EDIT
I had to modify the method because I was making UIKit calls in my completion block (e.g. updating labels etc). So it's actually a bit safer to call completion block on the main thread. (original version commented out)
The method you are trying to use is only available on iOS 5. For earlier OSes, consider using
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
and wrapping it into a new thread to achieve async behavior.
Both H2CO3 and Ken Thomases suggestions are right.
In addition, you could take a look at ios4-implementation-of-nsurlconnection-sendasynchronousrequestqueuecompletio.
If you use the main queue as the queue where the completion handler performs, you could use (as Tom suggested) the delegate pattern. To avoid duplicate code, you could use a wrapper on NSURLConnection delegates mechanism.
In the other case, if you want to maintain the async behaviour and you don't want to deal with sync call (as H2CO3 suggested, note that his suggestion is also valid) and the completion handler performs in a different queue, then I suggest you to wrap the async delegate pattern in a NSOperation class. This approach is quite difficult but you can find a good way of do this in Concurrent Operations Demystified (see both the posts).
Hope it helps.

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
}];
}