0

Creating first app with webservices, I am using AFNetworking for webservices. Everything is working fine but i have no idea , that how to fetch data out from block which i am getting in response. This is what i have done so far

+(WebServices *)sharedManager{


    static WebServices *managerServices = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        managerServices = [[self alloc] init];
    });
    return managerServices;

}

-(NSArray *)firstPostService{

       //1

    NSURL *url = [NSURL URLWithString:BaseURLString];
    //2

    AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    NSDictionary *param = @{@"request" : @"get_pull_down_menu" , @"data" : @"0,0,3,1"};




    [manager POST:@"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {



        [self methodUsingJsonFromSuccessBlock:responseObject];


    } failure:^(NSURLSessionDataTask *task, NSError *error) {

        UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];

        [av show];
    }];

    if (list.count == 0) {

        NSLog(@"Nothing in array yet!!");
    }
    else{
        NSLog(@"Object 1 is : %@", [list objectAtIndex:1]);

    }
        return list;


}


- (void)methodUsingJsonFromSuccessBlock:(id)json {
    // use the json

    NSString *string = [NSString stringWithUTF8String:[json bytes]];

    NSLog(@"This is data : %@", string);


   list = [string componentsSeparatedByString:@"\n"];

    NSLog(@"After sepration first object: %@", [list objectAtIndex:1]);

    //NSLog(@"json from the block : %@", json);
}

What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it

dispatch_async(dispatch_get_main_queue(), ^{

    data = [string componentsSeparatedByString:@"\n"];

    //WHERE DATA IS __block NSArray * data = [[NSArray alloc] init]; 
});

and i was returning it in the of the function(firstPostService) but nothing happen. i still get an empty array outside the block. Kindly help me , suggest me some good reading stuff. Thanking you all in advance.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
MQ.
  • 365
  • 6
  • 28
  • When the `POST` method wanted to return data to your method asynchonously, it used a completion block pattern. Thus, if your own `firstPostService` wants to return data asynchronously, it should _also_ employ the same completion block pattern. See http://stackoverflow.com/a/26174930/1271826 or point 3 of http://stackoverflow.com/a/20124755/1271826 or many other similar questions. – Rob Oct 04 '14 at 22:23
  • Rob thanks for your time, first problem is i am not familiar with such code its difficult to understand. I need this data to my view controller i am trying to return in dispatch part but it is not allowing. Is it possible to get data into my viewcontroller class ? – MQ. Oct 04 '14 at 23:01
  • Yeah, I know it's hard to wrap your head around it the first time you see it. You're dealing with asynchronous code where the completion blocks (of `POST` method, for example), are called well after the method that called it finishes. You cannot "return" data that won't be downloaded and parsed until much later. That's why `POST` used completion block pattern. And you should, too. See my answer below. – Rob Oct 05 '14 at 02:07

1 Answers1

3

You say:

I need this data to my view controller i am trying to return in dispatch part but it is not allowing. Is it possible to get data into my viewcontroller class ?

Yes, it's possible. But, no, firstPostService should not return the results. It can't because it returns immediately, but the POST completion blocks won't be called until much later. There's nothing to return by the time firstPostService returns.

At the end of your original question, you said:

What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it

   dispatch_async(dispatch_get_main_queue(), ^{

       data = [string componentsSeparatedByString:@"\n"];

       //WHERE DATA IS __block NSArray * data = [[NSArray alloc] init]; 
   });

This is not the appropriate pattern of __block local variable. You generally use that __block pattern when dealing with some block that runs synchronously (for example the block of an enumeration method). But while you can use __block variable with asynchronous block, you almost never do (and it doesn't quite make sense to even try to do it). When you use appropriate completion block patterns, there's no need for any __block variable.

So, let's go back to your original code sample: So, you should take a page from AFNetworking and employ completion blocks yourself. When the AFNetworking POST method wanted to return data to your code asynchonously, it used a completion block pattern, instead. Thus, if your own firstPostService wants to pass back data asynchronously, it should do the same.

For example:

@interface WebServices ()

@property (nonatomic, strong) AFHTTPSessionManager *manager;

@end

@implementation WebServices

// note, use `instancetype` rather than actually referring to WebServices 
// in the `sharedManager` method

+ (instancetype)sharedManager
{
    static id sharedMyManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedMyManager = [[self alloc] init];
    });
    return sharedMyManager;
}

// I'd also suggest that you init the `AFHTTPSessionManager` only once when this
// object is first instantiated, rather than doing it when `firstPostService` is
// called

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSURL *url = [NSURL URLWithString:BaseURLString];
        self.manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
        self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    }
    return self;
}

// Notice:
//
//   1. This now has a return type of `void`, because when it instantly returns, 
//      there is no data to return.
//
//   2. In order to pass the data back, we use the "completion handler" pattern.

- (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {

    NSDictionary *param = @{@"request" : @"get_pull_down_menu" , @"data" : @"0,0,3,1"};

    [self.manager POST:@"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
        NSArray *list = [self methodUsingJsonFromSuccessBlock:responseObject];
        if (completionHandler) {
            completionHandler(list, nil);
        }
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        [[[UIAlertView alloc] initWithTitle:@"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil] show];
        if (completionHandler) {
            completionHandler(nil, error);
        }
    }];

    //    // none of this code belongs here!!! You are dealing with asynchronous methods.
    //    // the `list` has not been returned by the time you get here!!! You shouldn't even
    //    // be using instance variable anyway!
    //
    //    if (list.count == 0) {
    //
    //        NSLog(@"Nothing in array yet!!");
    //    }
    //    else{
    //        NSLog(@"Object 1 is : %@", [list objectAtIndex:1]);
    //
    //    }
    //    return list;
}

- (NSArray *)methodUsingJsonFromSuccessBlock:(NSData *)data {
    // note, do not use `stringWithUTF8String` with the `bytes` of the `NSData`
    // this is the right way to convert `NSData` to `NSString`:

    NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    NSLog(@"This is string representation of the data : %@", string);

    // Note, retire the `list` instance variable, and instead use a local variable

    NSArray *list = [string componentsSeparatedByString:@"\n"];

    NSLog(@"After sepration first object: %@", [list objectAtIndex:1]);

    return list;
}

@end

Then, you could invoke that like so:

[[WebServices sharedManager] firstPostServiceWithCompletionHandler:^(NSArray *list, NSError *error) {
    if (error) {
        // handle the error here
    } else {
        // use the `list` results here
    }
}];

// NOTE, DO NOT USE `list` HERE. By the time you get here, `list` has not been 
// returned. Only use it in the above block.
// 
// In fact, you can see that if you put a `NSLog` or breakpoint here, and again, above
// where it says "use the `list` results` here", you'll see that it's running the code
// inside that block _after_ this code down here!

I'd suggest you tackle the above first, to first make sure you completely understand the proper asynchronous technique of the completion block pattern. We don't want to complicate things quite yet. Make sure you're getting the sort of data you wanted before you proceed to what I will describe below.

But, once you've grokked the above, it's time to look at your JSON parsing. You make several reference to JSON, but if that's what it really is, then using componentsSeparatedByString is not the right way to parse it. You should use NSJSONSerialization. Or even better, you can let AFNetworking do that for you (right now, you're making it more complicated than it needs to be and your results will not be formatted correctly).

Above, I kept your methodUsingJsonFromSuccessBlock in the process, but if you're really dealing with JSON, you should eliminate that method entirely. Let AFNetworking do this for you.

  1. You should eliminate the line that says:

    responseSerializer = [AFHTTPResponseSerializer serializer];
    

    The default serializer is AFJSONResponseSerializer which is what you want to use if handling JSON requests.

  2. The methodUsingJsonFromSuccessBlock is then no longer needed because AFNetworking will do the JSON conversion for you. So firstPostServiceWithCompletionHandler should look like:

    - (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {
    
        NSDictionary *param = @{@"request" : @"get_pull_down_menu" , @"data" : @"0,0,3,1"};
    
        [self.manager POST:@"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
            if (completionHandler) {
                completionHandler(responseObject, nil);
            }
        } failure:^(NSURLSessionDataTask *task, NSError *error) {
            [[[UIAlertView alloc] initWithTitle:@"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil] show];
            if (completionHandler) {
                completionHandler(nil, error);
            }
        }];
    }
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • ROB million Thanks :), the way u explained things (Y). But completion handler is still quite tricky. Now i have found a lecture on asyn req trying to understand the way it works. Thanks again ROB (Y) – MQ. Oct 05 '14 at 09:18