iosobjective-crestios7

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 don't get the data into an NSDictionary. The URL I call: http://myshop.com/api/articles.

Here is the source I have:

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

                           }];
    
 

}

Solution

  • 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.