NSURLConnection Returns No Data - ios

I have a URL that I'm trying to get XML from.
Now in my iOS app I have this, to get the data.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadDataUsingNSURLConnection];
}
- (void) loadDataUsingNSURLConnection {
NSString *url = #"http://64.182.231.116/~spencerf/university_of_albany/u_albany_alumni_menu_test.xml";
[self getMenuItems:url];
}
And then finally this,
- (void)getMenuItems:(NSString*)url{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(#"response == %#", response);
NSLog(#"data: %#", data);
/*
self.mealdata=[[MealData alloc]init:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.loadingView removeFromSuperview];
});
*/
}];
}
Now sometimes when I run my app it works great and data is returned, but then sometimes, I would say about 25% of the time, with me changing nothing in-between runs. It returns no data, and the NSLog returns
2015-03-10 18:28:05.472 APP[6289:97905] response == <NSHTTPURLResponse:
0x7fee7628e000> { URL: http://64.182.231.116/~spencerf/university_of_albany/u_albany_alumni_menu_test.xml } { status code: 200, headers {
"Accept-Ranges" = bytes;
Connection = "Keep-Alive";
"Content-Length" = 0;
"Content-Type" = "application/xml";
Date = "Tue, 10 Mar 2015 22:28:04 GMT";
Etag = "W/\"218ceff-0-510f6aa0317dd\"";
"Keep-Alive" = "timeout=5, max=90";
"Last-Modified" = "Tue, 10 Mar 2015 22:28:03 GMT";
Server = Apache;
} }
2015-03-10 18:28:05.472 App[6289:97905] data: <>
Not sure why this is happening, and I can't tell the difference between when it works and when it doesn't what is changing? So Im not sure how to fix this?
What I want is it to get the data every time?
Thanks for the help in adavence.

Try to increase timeout interval for your request:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setTimeoutInterval:60.0];//seconds

Your code is fine. It is the server that is giving you trouble.
I reloaded the page that you mentioned 20 times. It successfully loaded 13 times. 7 times it returned no error but also an empty response body.
What you can do is simply check for this condition in your app and run the request again to try again.
Or talk to the server owner to find out if they can improve the reliability of this service.

Running your code on device, when there is no data received, error message is either null or Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost.".
As to the latter error, you can see AFNetworking/issues/2314, below is a comment extracted from the post.
when iOS client received HTTP response with a Keep-Alive header, it
keeps this connection to re-use later (as it should), but it keeps it
for more than the timeout parameter of the Keep-Alive header and then
when a second request comes it tries to re-use a connection that has
been dropped by the server.
You could also refer to the solution here, if you can't change the server, I'd suggest you retry the request when error code is -1005 or received data length is 0.

Perhaps Long-Polling would help you. The web server intermittently returns data when I tried it.
Long-Polling will allow you to continue sending requests until you get the data you specify back.
This code shows you an example of how implement long-polling
- (void) longPoll {
//create an autorelease pool for the thread
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//compose the request
NSError* error = nil;
NSURLResponse* response = nil;
NSURL* requestUrl = [NSURL URLWithString:#"http://www.example.com/pollUrl"];
NSURLRequest* request = [NSURLRequest requestWithURL:requestUrl];
//send the request (will block until a response comes back)
NSData* responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
//pass the response on to the handler (can also check for errors here, if you want)
[self performSelectorOnMainThread:#selector(dataReceived:)
withObject:responseData waitUntilDone:YES];
//clear the pool
[pool drain];
//send the next poll request
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
-(void) startPoll {
//not covered in this example: stopping the poll or ensuring that only 1 poll is active at any given time
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
-(void) dataReceived: (NSData*) theData {
//process the response here
}

Your URL returns 404 Not Found, so you should fix server part

Related

IIS webdav and Ensembles

I'm trying to use sync through an IIS 8 Webdav backend with Ensembles. The problem i encounter is that the first sync works fine, but when i try to sync a second time or on a second unit (iPad in this case) I get a server error 405 "method not allowed". Has anyone encountered this and got it working, to sync with IIS Webdav?
This is the allheaderfield property of the server response:
" UserInfo={NSLocalizedDescription=HTTP status code was {
Allow = "COPY, PROPFIND, DELETE, MOVE, PROPPATCH, LOCK, UNLOCK";
Connection = "Keep-Alive";
"Content-Length" = 1293;
"Content-Type" = "text/html";
Date = "Mon, 25 Jan 2016 12:02:07 GMT";
"Persistent-Auth" = true;
Server = "Microsoft-IIS/8.5";
"X-UA-Compatible" = "IE=8";
EDIT:
It might be possible that this isn't a configuration problem after all. I added a few logs and the createDirectoryAtPath method gives me HTTP error 405, this is the original code:
- (void)createDirectoryAtPath:(NSString *)path completion:(CDECompletionBlock)completion{
NSMutableURLRequest *request = [self mutableURLRequestForPath:path];
request.HTTPMethod = #"MKCOL";
[request setValue:#"application/xml" forHTTPHeaderField:#"Content-Type"];
[self sendURLRequest:request completion:^(NSError *error, NSInteger statusCode, NSData *responseData) {
if (completion) completion(error);
}];}
And this is the directoryExistsAtPath method:
- (void)directoryExistsAtPath:(NSString *)path completion:(CDEDirectoryExistenceCallback)completion{
[self sendPropertyFindRequestForPath:path depth:0 completion:^(NSError *error, NSInteger statusCode, NSData *responseData) {
if (error && statusCode != 404) {
if (completion) completion(NO, error);
}
else if (statusCode == 404) {
if (completion) completion(NO, nil);
}
else {
CDEWebDavResponseParser *parser = [[CDEWebDavResponseParser alloc] initWithData:responseData];
BOOL succeeded = [parser parse:&error];
if (!succeeded) {
if (completion) completion(NO, error);
return;
}
BOOL isDir = [parser.cloudItems.lastObject isKindOfClass:[CDECloudDirectory class]];
if (completion) completion(isDir, nil);
}
}];}
If i replace the first parameter (currently the isDir-variable) in the completion block at the end to YES, the 405 error does not appear.On logging the parser.clouditems.lastobject, I find that it is often (or always?) empty). So setting the parameter to YES, results in data being uploaded to my webdav, and the folders are in place. However, testing on a second unit (or reinstalling the app on the same unit), download never happens - the downloadFromPath never gets called, a "GET"-request is never sent.
Looking at the calling code in the underlying framework (CDECloudmanager mostly) hasn't led me anywhere so far.
As the directoryExistsAtPath is optional, i tried commenting it out, but i don't think it made a difference.
Another thing I noticed is that I get several baseline files in the baselines folder. According to the Ensembles documentation, there should only be one.
Any clues?
Ok seems I got this working at last. I had to make a change in the CDEWebDavCloudFileSystem class to avoid the 405 error. Doing that however, I encountered a 404 error. That one was solved by configuring the IIS webdav.
So step 1, I changed the request in the sendPropertyFindRequestForPath method:
new code:
static NSString *xml = #"<?xml version=\"1.0\" encoding=\"utf-8\" ?><D:propfind xmlns:D=\"DAV:\"><D:allprop/></D:propfind>";
original code:
static NSString *xml = #"<?xml version=\"1.0\" encoding=\"utf-8\" ?><D:propfind xmlns:D=\"DAV:\"><D:prop><D:href/><D:resourcetype/><D:creationdate/><D:getlastmodified/><D:getcontentlength/><D:response/></D:prop></D:propfind>";
To remove the 404 error appearing after that I had to add mime type application/xml to the .cdeevent extension.
This link goes into detail of how to configure IIS for that:
http://anandthearchitect.com/2013/08/01/webdav-404file-or-directory-not-found/

error back when using nsurlsession to access a webservice several times in a for loop

I need to get image information from server, such image name, image id. Then use image id as one of parameters to make post, get image actual data. More specific, there are three images I should get.
First, I use getImageInfo to get image information.
- (void)getImageInfo {
// compose request
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:#"uid"];
NSString *checkCode = [getUserInfo objectForKey:#"checkCode"];
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#", uid, checkCode];
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetallshibietu"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPMethod = #"POST";
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
if (!error) {
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
// parse data in ram and put into images' imageInfos array
[self.images parseImageInfo:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[self getImageRawData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
}] resume];}
Then I use getImageRawData to get three image data.
- (void)getImageRawData {
// compose request dynamically
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:#"uid"];
NSString *checkCode = [getUserInfo objectForKey:#"checkCode"];
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSInteger count = 0;
for (ImageInformation *temp in self.images.imageInfos) {
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#&tupianid=%#", uid, checkCode, temp.imageId];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
// if client side is no errors, continue
if (!error) {
// if server side is no errors, continue
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
NSLog(#"图片内容:%#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// in ram and put into images' imageRawData array
[self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId withIndex:count];
// store data to disk
// NSString *path = [[NSString alloc] initWithFormat:#"image%#", temp.imageId];
// [FCFileManager writeFileAtPath:path content:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
}] resume];
count++;
}}
Here, it will loop three times, three responses come back, only the last one is complete, the others carry a error message, or incomplete raw data sometimes. Now I'm diving into concurrency programming guide, I guess serial queue likely can solve this problem.
Output like this:
2014-12-16 22:38:48.739 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:48.749 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:51.943 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>/9j/...(complete data)...9k=%%226654474.0</ns:return></ns:serviceGetthetupianResponse>
parameters of requests:
2014-12-17 14:59:25.364 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=41
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=42
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=43
the problem is likely not in composing request.
------------------------------------------------update1-----------------------------------------------
I have tried to put data task of session into a serial queue. Disappointed, this is not working.
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
});
Meanwhile, I make delegateQueue of session as nil, reference says if nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
Now I am still confused how to make it right.
-----------------------------------------------update2------------------------------------------------
I add [NSThread sleepForTimeInterval:0.5] into the block dispatched to serial queue.
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
[NSThread sleepForTimeInterval:0.5];
});
It does not work. The three responses are complete, but they are all the same.
Thank you in advance!
I'm just guessing as I've never tried it, but possibly your data tasks are all using the same TCP port on your end.
That would be OK if they were serialized - one after the other, in sequence - but if they overlap, then the server would receive garbled HTTP requests:
GET /foo
GET /bar
GET /baz
What the server would see might be something like:
GET /fGET /baroo
GET /baz
That your third requests actually works OK might be an accident of the timing.
If you absolutely require the three requests to be issued simultaneously, there are ways to open three different ports on your end. I don't know how to do it with Cocoa and Objective-C, but you can certainly do it with C and Berkeley Socket system calls. The Cocoa / Cocoa Touch networking methods are just wrappers around sockets.
A couple of thoughts:
Your technique of using a single NSMutableURLRequest instance, and repeatedly mutating it for each request (while the prior requests are still in progress) is curious.
In the spirit of thread safety, I would use a separate NSMutableURLRequest for each concurrent request. You don't want to risk having your thread issuing these requests mutate the request object while some background thread performing one of the prior requests. (See Apple's Thread Safety Summary in the Threading Programming Guide in which they point out that mutable classes are not generally thread safe.)
Having said that, the NSURLConnection documentation leaves us with the impression that this request object would be copied, mitigating this problem. I don't see this sort of assurance in the NSURLSession documentation (though I suspect it does the same thing).
I don't think this is the problem here (if this was the problem, the problem would likely be more erratic than what you report, and besides, I suspect that NSURLSession is handling this gracefully, anyway), but as a matter of good thread-safe coding habits, it would be prudent to let each concurrent request have its own NSMutableURLRequest object.
You have confirmed that the information being used in the requests looks valid.
If you wanted to take this to the next level, you might use Charles (or Wire Shark or whatever tool you prefer) to observe the actual requests as they go out. These sorts of tools are invaluable for debugging these sorts of problems.
If you observe the requests in Charles and confirm that they are valid, then this categorically eliminates client-side issues from the situation.
What is curious is that you are not receiving NSError object from dataTaskWithRequest. Nor are you receiving statusCode other than 200 from your server. That means that your requests were successfully sent to the server and received by the server.
Instead, the server is processing the request, but is having a problem fulfilling the request. This leads me to wonder about the server code, itself. I suspect that there is something in the server code that is preventing concurrent operations from taking place (e.g., locking some shared resource, such as temp file or SQL table, for the duration of the request). I would take a hard look at the server code and make sure there are no potential contention issues.
Furthermore, I would modify the server code to not simply report "error", but rather to produce a meaningful error message (e.g. system provided error messages, error codes, etc.). Your server is detecting an error, so you should have it tell you precisely what that error was.
Note, I am explicitly not advising you to make your requests run sequentially. That is inadvisable. While it might solve the immediate problem, you pay a huge performance penalty doing that, and it's not scalable. And remember, you really must handle concurrent requests gracefully, as you're likely to have multiple users of the app at some point.
I would take a hard look at the server code, adding further debugging information to the error messages in order to track down the problem.
I put request into for loop, it works. The first thought of rob about NSMutableRequest and NSURLSession seems right, I'm trying to catch the whole idea. Thanks for rob's answer. Anyway, this is code.
for (ImageInformation *temp in self.images.imageInfos) {
// compose request dynamically
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#&tupianid=%#", uid, checkCode, temp.imageId];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];
// data task
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
// if client side is no errors, continue
if (!error) {
// if server side is no errors, continue
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
// in ram and put into images' imageRawData array
[self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId];
// store data to disk
// [FCFileManager writeFileAtPath:path content:data];
// dispatch display image task to main
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.images.imageDrawDatasDic count] == [self.images.imageInfos count]) {
[self.tableView reloadData];
}
});
}
}
}] resume];
[NSThread sleepForTimeInterval:0.5];
});
}
}

How to fix NSURLErrorDomain error -1012 in authentication?

I'm dealing the authenticate issue with Tumblr account using [NSURLConnection sendAsynchronousRequest:queue:completionHandler:] to send the authenticate request, but here I meet a tough problem:
Whenever I send the request at the first time, everything goes perfectly, but when the first authentication is done and then resend the request second time, there comes "NSURLErrorDomain error -1012".
The authenticate page is loaded in a webview so that the authentication should be done in my app without a browser. But it is interesting that if the process runs in a browser there comes no error, errors only happen when using webview.
It was weird that the authentication goes with the same code, but only the first authentication can be done, only if I reinstall the app can I authenticate it again, and after this the problem comes again.
I did everything I can chase to solve the issue, I clean the cache and cookie in webview, step the authentication process to see parameters, set the cachePolicy of the request but nothing helps.
I also found that on ios6 the process goes without any error. But on ios7 I get the -1012.
code -1012 tells me that the user cancelled the authentication, but the process goes automatically and I do not cancel it.
I'm wondering if the problem comes from the NSURLConnection.
- (void)authenticate:(NSString *)URLScheme WithViewController:(UIViewController *)con callback:(TMAuthenticationCallback)callback {
self.threeLeggedOAuthTokenSecret = nil;
self.hostViewController = con;
self.callback = callback;
[self emptyCookieJar];
NSString *tokenRequestURLString = [NSString stringWithFormat:#"http://www.tumblr.com/oauth/request_token?oauth_callback=%#", TMURLEncode([NSString stringWithFormat:#"%#://tumblr-authorize", URLScheme])];
NSLog(#"%#", tokenRequestURLString);
NSMutableURLRequest *request = mutableRequestWithURLString(tokenRequestURLString);
NSLog(#"%#", request);
[[self class] signRequest:request withParameters:nil consumerKey:self.OAuthConsumerKey
consumerSecret:self.OAuthConsumerSecret token:nil tokenSecret:nil];
[self openOAuthViewController];
NSURLConnectionCompletionHandler handler = ^(NSURLResponse *response, NSData *data, NSError *error) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (error) {
if (callback) {
callback(nil, nil, error);
}
return;
}
NSLog(#"%d", statusCode);
if (statusCode == 200) {
self.threeLeggedOAuthCallback = callback;
NSDictionary *responseParameters = formEncodedDataToDictionary(data);
self.threeLeggedOAuthTokenSecret = responseParameters[#"oauth_token_secret"];
NSURL *authURL = [NSURL URLWithString:
[NSString stringWithFormat:#"http://www.tumblr.com/oauth/authorize?oauth_token=%#",
responseParameters[#"oauth_token"]]];
[self initOAuthViewControllerWithURL:authURL];
} else {
if (callback) {
callback(nil, nil, errorWithStatusCode(statusCode));
}
}
};
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:handler];
}
Code above, everything goes normally before [NSURLConnection sendAsynchronousRequest:queue:completionHandler:],and after this method I got the error in completionHandler.

objective c ios 7 get data from rest api

i want to get all articles from the shopware api(http://wiki.shopware.de/Shopware-API_cat_919.html)
but the i dont get the data into an NSDictionary
url i call: http://myshop.com/api/articles
here is the source i got
NSURL *url = [NSURL URLWithString:weburl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError) {
if (data.length > 0 && connectionError == nil) {
NSDictionary *rest_data = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
_newsDataForTable = [NSMutableArray array];
NSDictionary *news;
for (id key in rest_data[#"postalcodes"]) {
news = [rest_data[#"postalcodes"] objectForKey:key];
}
int iterator = 0;
for (id key in news) {
[_newsDataForTable insertObject:key[#"title"] atIndex:iterator];
iterator++;
}
[_newsTable reloadData];
[_newsTable numberOfRowsInSection:[_newsDataForTable count]];
[_newsTable reloadRowsAtIndexPaths:0 withRowAnimation:UITableViewRowAnimationLeft];
}
}];
}
There are a couple of things in your approach that could use improvement.
First, this is performing networking on the main queue. That is a no-no, wether the networking is synchronous or not. Creating a new NSOperationQueue for your connections and passing that instead of [NSOperationQueue mainQueue] is a huge improvement.
Second, the error handling is incorrect. In general the correct error handling pattern for Objective-C is to check wether a call resulted in the expected result before using the error. In this case, it's the NSURLResponse that should be checked, not the data. NSURLConnection may be able to connect to the remove service just fine but get no data back - and for many HTTP requests this is expected, correct behavior. If there is a problem connecting, the NSURLResponse will be nil. Check wether the response is nil, if it is then handle the error.
You're also not checking the HTTP response status code or MIME type. The server could respond with a 500, indicating a server error, or could mistakenly send you HTML (which would give the JSON parser fits).
A verbose example that does the above correctly is here. :
[NSURLConnection sendAsynchronousRequest:request queue:[self connectionQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (response != nil){
if ([[self acceptableStatusCodes] containsIndex:[(NSHTTPURLResponse *)response statusCode] ]){
// The server responded with an HTTP status code that indicates success
if ([[self acceptableMIMETypes] containsObject:[[response MIMEType] lowerCaseString] ]){
// The server responded with a MIME type we can understand.
if ([data length] > 0){
NSError *jsonError = nil;
id jsonObject = nil;
// The server provided data in the response, which means we can attempt to parse it
// Note that we are not specifying NSJSONReadingMutableContainers or NSJSONReadingMutableLeaves, as this would result in
// an object that is not safe to use across threads.
jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
if (jsonObject != nil){
// The JSON parser successfully parsed the data, and returned an object. There is nothing to tell us what kind of object was returned.
// We need to make sure it responds to the selectors we will be using - ideally, we'd pass this object to a method that takes an
// id parameter, not NSDictionary, and inside that method it would check wether the id object responds to the specific selectors
// it is going to use on it.
if ([jsonObject respondsToSelector:#selector(dictionaryWithDictionary:)]){
[self doStuffWithDictionary:jsonObject];
}
} else {
// The JSON parser was unable to understand the data we provided, and the error should indicate why.
[self presentError:jsonError];
}
} else {
// The server responded with data that was zero length. How you deal with this is up to your application's needs.
// You may create your own instance of NSError that describes the problem and pass it to your error handling, etc.
}
} else {
// The server response was a MIME type we could not understand. How you handle this is up to you.
}
} else {
// The server response indicates something went wrong: a 401 Not Found, etc.
// It's up to your application to decide what to do about HTTP statuses that indicate failure.
// You may create your own instance of NSError that describes the problem and pass it to your error handling, etc.
}
} else {
// Only inspect the error parameter if the response is nil.
// The error indicates why the URL loading system could not connect to the server.
// It is only valid to use this error if the server could not connect - which is indicated by a nil response
[self presentError:connectionError];
}
}];
// Returns the HTTP status codes we find acceptable.
- (NSIndexSet *) acceptableStatusCodes {
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 99)];
}
// Returns the mime types we can accept and understand.
- (NSSet *) acceptableMimeTypes {
NSSet *result = nil;
result = [NSSet setWithObjects:#"application/json", #"application/json; charset=utf-8", nil];
return result;
}
// Generic error handling method.
- (void) presentError:(NSError *)error {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
}
Yup, that's a lot of code, and it should be broken into smaller methods - but it illustrates the logic that should be implemented.
The NSError you are getting now
In your comments you indicate that you are getting an NSError with the domain NSURLErrorDomain and code -1002. If you look at NSURLErrors.h, you will see that NSURL errors map to CFURL errors. If you look at CFNetworkErrors.h, you can see that error code -1002 is kCFURLErrorUnsupportedURL. The URL loading system thinks the URL you are using is not a supported type. This is most likely because the scheme of your URL is incorrect, or how you are attempting to pass credentials as part of the URL is incorrect. Elsewhere in your comments you indicate you are passing credentials as follows:
username:apikey:someurl.com/foo/
Which should be more like:
https://username:apikey#someurl.com/foo/
But only if the service you are accessing is using a supported HTTP authentication type (i.e. Basic authentication). Either way, correctly composing the URL will fix the error you are currently seeing.

sendSynchronousRequest/connectionDidFinishLoading

I'm using synchronous requests for the first time and would love some help. (The code I'm writing is solely for my own use, and given its purposes synchronous requests are not a problem.)
The code gets data from a web page in a series, manipulates the data, moves on to the next page in the series, manipulates THAT data, and so on. I'm using a synchronous request because I need the connection to finish loading and the data to be manipulated before the function loops to the next page.
Here's my looping code:
-(NSData *)myMethod {
NSString *string;
NSData *data;
for (int x = 1; x<100; x++) {
string = [NSString stringWithFormat:#"http://www.blahblah.com/%d",(x)];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:string]];
NSURLResponse *response = nil;
NSError *error = nil;
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
return data;
}
When I was using connectionWithRequest, I just put the code to manipulate the data in connectionDidFinishLoading and it worked fine. But with sendSynchronousRequest, even though NSLog shows that the loop code is looping, the code in connectionDidFinishLoading never runs.
How can I fix this?
(Or am I taking the wrong approach completely?)
Here's how to take #nhgrif's good advice to perform asynch and preserve all of the results.
- (void)doRequest:(NSInteger)requestIndex gatheringResultsIn:(NSMutableArray *)array completion:(void (^)(void))completion {
if (requestIndex < 100) {
NSString *string = [NSString stringWithFormat:#"http://www.blahblah.com/%d",(requestIndex)];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:string]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) [array addObject:data];
[self doRequest:requestIndex+1 gatheringResultsIn:array completion: completion];
}];
} else {
completion();
}
}
This will run 100 requests indexed 0..99 placing the results in a mutable array. Call it like this:
NSMutableArray *results = [NSMutableArray array];
[self doRequest:0 gatheringResultsIn:results completion:^{
NSLog(#"100 NSData objects should be here: %#", results);
}];
connectionDidFinishLoading is an NSURLConnection delegate method for when you've sent asynchronous requests. Normally, you'd implement this method to get the data that loaded, but you don't need to do this, as it's returned synchronously and assigned to your data variable.
I will note however, you are definitely taking a poor approach here.
First of all, if you'd use asynchronous requests here, you could query all 100 URLs as basically the same time and let them return in their own time.
But what's more problematic is what actually happens with your code.
We create a URL, send the synchronous request, and when it finishes, assign the return to data.
... then we loop. And do this 99 times. 99 times we make this synchronous request (to a different URL each time) and overwrite the data that the previous request loaded. And after the 100th time, we exit the loop and return the data we downloaded in the final request.

Resources