1

I'm starting to learn Objective-C for iOS Development, and a got a issue that is driving me crazy.

All that I want is to do a request, retrieve e JSON and then set this JSON into an instance property.

-(NSArray *) retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        NSDictionary *dict = (NSDictionary *) JSON;
        [self setJSONObjectsCollection: [dict objectForKey:rootNode]];

    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        NSLog(@"Communication Error: %@", error);
    }];

    [operation start];

    return _JSONObjectsCollection;
}

-(void) setJSONOBjectsCollectionAttribute: (NSArray *) arrayWithCollection
{
    NSLog(@"Outside Method %@", arrayWithCollection);
    self.JSONObjectsCollection = arrayWithCollection;
}

However, my self.JSONObjectsCollection property are ok inside the block, but outside is always null.

Can you help me guys ?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Diego Charles
  • 103
  • 12

1 Answers1

4

It's because the setting of JSONObjectsCollection happens asynchronously. So your method is returning JSONObjectsCollection before it is set.


Thus, it might look like:

- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        NSDictionary *dict = (NSDictionary *) JSON;
        [self setJSONObjectsCollection: [dict objectForKey:rootNode]];

        // do here whatever you want to do now that you have your array, e.g.
        //
        // [self.tableView reloadData]; 

    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        NSLog(@"Communication Error: %@", error);
    }];

    [operation start];
}

Note, retrieveAtEndpoint now has a void return type, but in the completion block, I'm invoking whatever code you want to perform once the JSON objects collection has been updated.


If this is a method inside your model object, but you want to provide an interface by which the view controller can supply a block of code that should be executed upon successful retrieval of the JSON, use a completion block:

- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(void (^)(NSError *error))completion
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        NSDictionary *dict = (NSDictionary *) JSON;
        [self setJSONObjectsCollection: [dict objectForKey:rootNode]];

        if (completion)
        {
            completion(nil);
        }

    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        if (completion)
        {
            completion(error);
        }
    }];

    [operation start];
}

Or, if you want to simplify your use of a block parameter, you can define a type for the completion block at the start of your model object's .h file (before the @interface block):

typedef void (^RetrievalCompleteBlock)(NSError *);

And then the method is simplified a bit:

- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(RetrievalCompleteBlock)completion
{
    // the code here is like it is above
}

Anyway, regardless of whether you use the typedef or not, the view controller could do something like:

ModelObject *object = ...
NSString *rootNode = ...
[object retrieveAtEndpoint:url withRootNode:rootNode completion:^(NSError *error) {
    if (error)
    {
        // handle the error any way you want, such as

        NSLog(@"%s: retrieveAtEndPoint error: %@", __FUNCTION__, error);
    }
    else
    {
        // do whatever you want upon successful retrieval of the JSON here
    }
}];

The details here will vary based upon how your view controller is accessing the model object, knows that the root node should be, etc. I often will include another parameter to my completion block which is the data being retrieved, but given that you updated your model object and can access it that way, perhaps that's not necessary. I simply don't have enough details about your implementation to know what is right here, so I kept my implementation as minimalist as possible.

But hopefully this illustrates the idea. Give your retrieveAtEndpoint method a completion block parameter, which lets the view controller specify what it wants to do upon completion (or failure) of the communication with the server.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • @DiegoCharles It's hard to say because you don't show us what you're trying to do with this array after setting it. But you could just have the completion block that's setting `JSONObjectsCollection` then do, for example, the `reloadData` for your table view or whatever. – Rob Jun 25 '13 at 21:26
  • This method belongs to a model class that handles a specific entity (ex: User, Customer, Activity...), and all that I want is isolate those responsibilities, calling "retrieveAtEndpoint" method just to assign the retrieved data into a property, so I can handle it later on the appropriate ViewController – Diego Charles Jun 26 '13 at 15:40
  • @DiegoCharles The typical solution is to give your `retrieveAtEndpoint` another parameter, a completion block, so that the view controller can specify what should be done when the download is finished. See my revised answer. – Rob Jun 26 '13 at 16:42