I'm using the following to request data using NSJSONSerialization. The problem I'm having is that if the data is inaccessible (e.g. no network connection) the app crashes. How could I go about stopping the app from crashing if the network or server is down?
I'm calling [self requestData]; in the viewDidLoad: method
-(void)requestData {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:#"http://example.com/api/nodes"]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSError *jsonParsingError = nil;
NSDictionary *publicData = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParsingError];
publicDataArray = [publicData objectForKey:#"data"];
for(publicDataDict in publicDataArray) {
NSLog(#"data output is %#",[publicDataDict objectForKey:#"title"]);
}
}
thanks for any help
Some thoughts:
Use Reachability for checking network connection
Always use asynchronous request, else it'll block your UI till the app get the response from server.
Always use exception handling
Here the issue is:
You are calling a synchronous request in the viewDidLoad using sendSynchronousRequest. But the server is down, so you won't get the result, and it still expect any data to come. But your app won't load untill that request finishes. Due to this springboards application-watchdog will terminate your app.
What is Watch dog ?
watchdog — In order to keep the user interface responsive, iOS
includes a watchdog mechanism. If your application fails to respond to
certain user interface events (launch, suspend, resume, terminate) in
time, the watchdog will kill your application and generate a watchdog
timeout crash report. The amount of time the watchdog gives you is not
formally documented, but it's always less than a network timeout.
Please check this Technical question on Apple site.
Why don't you check if [NSURLConnection sendSynchronousRequest:] got any error?
NSError *requestError = nil;
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&requestError];
if (requestError)
{
NSLog(#"sync. request failed with error: %#", requestError);
}
else
{
// handle data
}
And you really should check if NSJSONSerialization had an error too:
NSError *jsonParsingError = nil;
NSDictionary *publicData = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParsingError];
if (jsonParsingError)
{
NSLog(#"JSON parsing failed with error: %#", jsonParsingError);
}
else
{
// do something
}
Related
when i am running my app in the simulator everything is working perfectly .But when i running the same app in the Ipad exception is being thrown.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'data parameter is nil.
In my app at one step i have to request a web-URl and need to parsed the returned JSON response. But I have checked the web-url and have been able to parse perfectly in simulator. But all the problem has been arisen in real ios device.But I think i have identified the code where it is getting wrong.
+ (NSDictionary*) getParsedJSON:(NSString*) urlString {
NSLog(#"################################################################################");
NSLog(#"getParsedJSON => urlString:");
NSLog(#"%#", urlString);
NSURL* url = [NSURL URLWithString:urlString];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
NSURLResponse *response1 = nil;
NSError *error = nil;
NSData* response = [NSURLConnection sendSynchronousRequest:request returningResponse:&response1 error:&error];
//NSData* response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSLog(#"--------------------------------------------------------------------------------");
NSString* responseString = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSLog(#"getParsedJSON => responseString:\n%#", responseString);
NSLog(#"--------------------------------------------------------------------------------");
NSError* jsonParsingError = nil;
NSDictionary* parsedJSON = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonParsingError]; // here is place where exception seems to be thrown.
if (jsonParsingError) {
NSLog(#"ERROR in parsing JSON: %#", jsonParsingError);
} else {
NSLog(#"getParsedJSON => parsedJSON: \n%#", [parsedJSON description]);
}
NSLog(#"################################################################################");
return parsedJSON;
}
I have identified the line where it seems to be wrong .I have also attached screen shot of the exception report..Hoping for your experienced reply.
AS we can see from the logs your response string is null while you are using it on your Device. This may be due to some internet access problem. Try to Use:
if([response isequaltostring:#"(null)"]||response == nil || response.length == 0)
{
NSError* jsonParsingError = nil;
NSDictionary* parsedJSON = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonParsingError]; // here is place where exception seems to be thrown.
if (jsonParsingError) {
NSLog(#"ERROR in parsing JSON: %#", jsonParsingError);
}
else {
NSLog(#"getParsedJSON => parsedJSON: \n%#", [parsedJSON description]);
}
}
Also try to add the exceptional breakpoint and post where exactly the app crashed.
Let me know the result.
First, you need to set an exception breakpoint in Xcode - there are many posts here on how to do that. Second, after each of you statements where an object is created or returned, add an assert:
NSURL *foo = ...
assert(foo);
Doing this will help you find the first issue not the last one.
As per your logs, your response string is empty!
Do the below two things!
Add NSLog(#"Response Data: %#",response); and check if the response has value?
If 'response' has value, convert it to a string - Log the string value - And check if the any of the key has nil value?
'NSJSONSerialization JSONObjectWithData' method would crash if it finds any key with nil value.
I m using sendSynchronousRequest to get the data from the server. I know that synchronous will wait until the data received for that request.
But the problem comes when user by mistake enters some non-existing url and than tries to get response. In this case, if user goes in to background and than comes into foreground it shows only black screen. It only shows status bar. Also its not showing any background application. I have to press Home button to come out of my application.
On simulator, After 1+ minute it shows me the message that "Request time out" (No crash).
On Device, within 1 min application get crashes.
Any suggestion. Any Help. This is really a serious issue in my app.
Thanks.
Just like Julien said, the watchdog is killing your app. To answer some questions:
why does this happen only on the simulator?
Because when you're debugging the watchdog leaves your app alone, it can take time.
why does this happen only when the user enters a wrong url?
Because of the system timeout, the system will keep trying for 60 secs if it can't find a server.
so the problem is synchronous vs asynchronous?
No, the problem is the thread, you can do the same operation in a background thread, just don't do it on the main thread and the watchdog will leave you alone.
why is the screen black when the app comes up?
Remember, you are making blocking stuff on the main thread, the thread that draws...
Hope that was all. Let me know if I missed something.
Why not setting a timeout for your connection?
NSString *urlString = TEST_CONNECTION;
NSError *error = nil;
NSHTTPURLResponse *response = nil;
NSURLRequest *request = [NSURLRequest
requestWithURL:[NSURL URLWithString:urlString]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:5.0];
NSData *conn = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
This should release the synchronous waiting after a number of seconds, which should solve your problem without going with an asynchronous call (which sometimes isn't the proper solution)
I know this works properly because this is how I check if I am connected to a certain VPN (where reachability flags totally fail).
you should take a look to this article: https://developer.apple.com/library/ios/#qa/qa1693/_index.html
iOs contains a watchdog, if your application is blocked to much time on an operation on the main thread, this one will be killed. (for more details about Watchdog: http://en.wikipedia.org/wiki/Watchdog_timer)
So if you want to download something, don't download it on the main thread.
RELATE
UIImage *image = [self.imgCache objectForKey:urlString];
if(!image){
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60.0];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSLog(#"%#",response);
UIImage *img = [UIImage imageWithData:data];
//
if(img)
{
dispatch_sync(dispatch_get_main_queue(), ^{
[self.imgCache setObject:img forKey:urlString];
completionBlock(img);
});
}
});
}
else{
completionBlock(image);
}
use ASIHttpRequest class instead of NSURLConnection , its nothing but wrapper around NSURLConnection and has very simple callbacks , you can also set time to complete a request. Please go through this link for more info http://allseeing-i.com/ASIHTTPRequest/
I think you first have to test user data whether it is correct or not and than only if it is correct, sends the request otherwise prompt user that "please enter correct data"...
or
when your parsing of data in response failed. You can also make protocol delegate method i.e FinishWithError so that you come up with your last UI.
Try this one:
#import "ASIHTTPRequest.h"
//In a method
[self performSelectorInBackground:#selector(DownLoadImageInBackground:) withObject:imgUrlArr];
-(void) DownLoadImageInBackground:(NSArray *)imgUrlArr1
{
NSURL * url = [Image URL];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
-(void)requestFailed:(ASIHTTPRequest *)request
{
NSLog(#"URL Fail : %#",request.url);
NSError *error = [request error];
// you can give here alert too..
}
-(void)requestFinished:(ASIHTTPRequest *)request
{
NSData *responseData = [request responseData];
UIImage *imgInBackground = [[UIImage alloc]
initWithData:responseData];
[imageView setImage: imgInBackground];
}
This might help you: I am also loading a number of images at one time, so images that have no proper data show a black screen. To avoid this, try to resize your imageview.
You could check the reachability of the URL before starting the request.
Apple has Reachability Methods to do so. But its easier to use a wrapper. E.g. ASIReachability.
I think the application crashing because you does not get any data when user enters wrong URL and you are using this 'returned' nil NSData to do stuffs.
I think this will fix your problem
NSData *data=[NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if(data!=nil){
///
} else {
NSLog(#"NO DATA");
}
I am sending an http request from iOS (iPad/iPhone) to my python google app engine server using the NSURLConnection and NSURLRequest classes.
How do I read the response's status, i.e. the value set by app engine using response.set_status(200, message="Success") for instance?
I'm not able to find where I can read these status codes once I receive the NSURLConnection's connectionDidFinishLoading delegate call on the client end.
If you are sending a synchronous request, you could get the response code from NSHTTPURLResponse.
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:URL_LOGIN]];
NSData *respData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"~~~~~ Status code: %d", [response statusCode]);
Hope this will help you :)
The connection:didReceiveResponse: delegate method is called when a response is received, which gives you an NSURLResponse to play with.
If you've made an HTTP request, then it'll actually be an NSHTTPURLResponse object, which has a statusCode method.
I use ASIHTTPRequest to do http requests in my iPhone app. ASIHTTPRequet comes with that feature that starts the activity indicator when issuing a request and stops it when finished. The problem is, once I started a request the indicator never stops and keeps spinning as long as my app runs.
Here is my code, a little utility method that fetches some content from the web synchroniously (since it gets started in a different thread):
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL: [NSURL URLWithString: url]];
[request startSynchronous];
NSError *error = [request error];
NSString *response = nil;
if (error) {
NSLog(#"error %#", error);
return nil;
}
int statusCode = [request responseStatusCode];
response = [NSString stringWithString: [request responseString]];
NSLog(#"status code: %d response: %#", statusCode, response);
if (statusCode != 200) {
return nil;
}
return response;
The above code works just fine, I get the contents of the given URL as a NSString only the indicator keeps spinning. My question is: Why does the indicator never stop and how to fix it? Do I have to release some resources here?
This is a bug that was fixed very recently in the development version of ASIHTTPRequest:
http://github.com/pokeb/asi-http-request/commit/35ea592084145b3332861344f36b52dbcaafa351
(It only affects synchronous requests started on a secondary thread)
Can you try the same thing with an asynchronous request and see if that changes it? I use ASIHTTPRequest and I've never noticed this behavior, but I also never use synchronous requests.
I need to check if a file exists on my server without using cache. The methods I have used are all returning a 200, even if the file does not exist, so I can only assume there is a cache problem, or theres a problem with my code.
Heres my code: for arguments sake..the URL is changed in this example, but the url is correct in my code.
NSString *auth = [NSString stringWithFormat:#"http://www.mywebsite.com/%#.txt",[self aString]];
NSURL *authURL = [NSURL URLWithString:auth];
NSURLRequest* request = [NSURLRequest requestWithURL:authURL
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:5.0];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request
delegate:self];
NSHTTPURLResponse* response = nil;
NSError* error = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"statusCode = %d", [response statusCode]);
if ([response statusCode] == 404)
NSLog(#"MISSING");
else
NSLog(#"EXISTS");
the response is always 200, even if I rename the file on the server.
There are a couple of potential problems with your code. First, when you create conn using connectionWithRequest:delegate: you are starting an asynchronous request. The response would be received in the delegate's (self in your case) connection:didReceiveResponse: method. Are you trying to do the request asynchronously? From the rest of your code though, it looks like you are actually trying to do a synchronous request. That's what sendSynchronousRequest:returningResponse:error: is for. If that's what you intend, then you don't need the earlier call to create a connection.
Assuming thats the case, you need to capture and check the value returned from calling sendSynchronousRequest:returningResponse:error:. It will return nil if the connection failed, which is what I suspect is happening. You can then look at the error returned to figure out what is going on. Try something like:
NSData * result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (result != nil) {
NSLog(#"statusCode = %d", [response statusCode]);
if ([response statusCode] == 404)
NSLog(#"MISSING");
else
NSLog(#"EXISTS");
} else {
NSLog(#"%#", error);
}
Is it possible it's caching on the server side? If so you could try NSURLRequestReloadIgnoringLocalAndRemoteCacheData instead of NSURLRequestReloadIgnoringCacheData.