1

I'm learning the NSURLSession class and I wanted to create a separate class in my project with all the methods I use to retrieve data from a web service.

This is the header file, tclass.h

#import <Foundation/Foundation.h>

@interface tclass : NSObject

- (void)returnIP;
@property (nonatomic, strong) NSString *IP ;

@end

This is the implementation file, tclass.m

#import "tclass.h"

@implementation tclass

- (id)init {
  self = [super init];
  return self;
}

- (void)returnIP {
  NSURLSession *session = [NSURLSession sharedSession];
  [[session dataTaskWithURL:[NSURL URLWithString:@"http://icanhazip.com/"]
    completionHandler:^(NSData *data,
        NSURLResponse *response,
        NSError *error) {
      [self setIP:[NSString stringWithUTF8String:[data bytes]]];
    }] resume];
}


@end

In my ViewController class, I firstly use the returnIP method to retrieve the data and to store it in a property then I update the UI, all of this using dispatch_async.

- (IBAction)butact:(id)sender {
  tclass *t = [[tclass alloc]init];

  [_round setHidden:NO];
  [_round startAnimating];

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
      [t returnIP];
      dispatch_async(dispatch_get_main_queue(), ^{
        [_round stopAnimating];
        [_round setHidden:NO];
        [_lab setText:[t IP]];
        });
      });

}

My problem is that the IP property is not populated, and the UI update does not appear to be done even though I'm using the main queue to do it.

How can I accomplish this process?

Is my approach completely wrong?

Thanks for your help.

PeppeLaKappa
  • 139
  • 11
  • 1
    Use completion block parameter pattern: http://stackoverflow.com/a/26174930/1271826 – Rob Nov 02 '14 at 13:58

1 Answers1

0

Thanks to @Rob who commented my post I implemented a solution based on blocks.

Here's the updated class header

#import <Foundation/Foundation.h>

@interface tclass : NSObject

- (void)returnIP:(void (^)(NSString *ip))completionHandler;

@end

and the implementation

#import "tclass.h"

@implementation tclass

- (id)init {
    self = [super init];
    return self;
}

- (void)returnIP:(void (^)(NSString *ip))completionHandler {

    NSURLSession *session = [NSURLSession sharedSession];
    [[session dataTaskWithURL:[NSURL URLWithString:@"http://icanhazip.com/"]
            completionHandler:^(NSData *data,
                                NSURLResponse *response,
                                NSError *error) {
                if(completionHandler) {
                    completionHandler([NSString stringWithUTF8String:[data bytes]]);
                }
            }] resume];
}

@end

The mechanism used to call this method is a bit changed:

- (IBAction)butact:(id)sender {
    tclass *t = [[tclass alloc]init];

    [_round setHidden:NO];
    [_round startAnimating];

    [t returnIP:^(NSString *ip) {
        NSLog(@"%@", ip);
        dispatch_async(dispatch_get_main_queue(), ^{
            [_round stopAnimating];
            [_round setHidden:YES];
            [_lab setText:ip];
        });
    }];

}
PeppeLaKappa
  • 139
  • 11
  • 1
    It would be better to include a way to pass an error back (if one should occur) in your block as well. You could either change the return type to id, and check whether it's a string or an NSError object, or have the completion handler return a string and an error object (one of which would be nil in any single return). – rdelmar Nov 02 '14 at 16:06
  • Thanks for your comment, I'm still learning how to manage errors in Obj-C and returning both the string and the error object seems to be the best choice. – PeppeLaKappa Nov 03 '14 at 08:49