Reputation: 672
I have a UITableViewController, call it TableViewControllerA, that's the delegate of another object, APICallerB, that I've created to communicate with an API. Through an NSURLSessionDataTask, APICallerB is setting one of its properties, which will then be set equal to one of TableViewControllerA's properties.
Here's tableViewControllerA's viewDidLoad
method:
- (void)viewDidLoad {
[super viewDidLoad];
// init instance of APICallerB
APICallerB *acb = [[APICallerB alloc] init];
// Set TableViewControllerA as delegate
tvcA.delegate = self;
[acb makeAPICallWithArgument:self.argument];
self.property1 = acb.property2;
}
My question is: What's the best way to to wait for [acb makeAPICallWithARgument:self.argument]
to complete, so that self.property1 = acb.property2
will work? I'm assuming GCD could be used (`dispatch_sync'?) but, being new to iOS/Objective-C, I'm not sure where to use it. Or would it be better move one or both of those items elsewhere?
Here's the method from APICallerB:
- (void)makeAPICallWithArgument:(NSString *)arg
{
NSString *requestString = [NSString stringWithFormat:@"http://%@:%@@apiurl.com/json/Request?arg=%@", API_USERNAME, API_KEY, arg];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
config.HTTPAdditionalHeaders = @{@"Accept" : @"application/json"};
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSArray *ar = [jsonObject[@"Result"] objectForKey:@"results"];
self.property2 = ar;
}];
[dataTask resume];
}
Upvotes: 1
Views: 1349
Reputation: 437582
You are calling asynchronous method so you should employ asynchronous pattern. For example, the completion block implementation might look like:
- (void)makeAPICallWithArgument:(NSString *)arg completionHandler:(void (^)(NSArray *results, NSError *error))completionHandler
{
NSString *requestString = [NSString stringWithFormat:@"http://%@:%@@apiurl.com/json/Request?arg=%@", API_USERNAME, API_KEY, arg];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
config.HTTPAdditionalHeaders = @{@"Accept" : @"application/json"};
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil, error);
});
} else {
NSError *parseError;
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(jsonObject[@"Result"][@"results"], parseError);
});
}
}
}];
[dataTask resume];
}
And you'd call it like:
[acb makeAPICallWithArgument:self.argument completionHandler:^(NSArray *results, NSError *error){
// you'd probably first check to make sure you didn't have an error
self.property1 = results;
}];
A couple of minor observations:
I probably wouldn't create a new session every time. Save this session for future requests if you expect to do more than one call.
Your makeAPICallWithArgument
is updating a property, which you are trying to retrieve later. I'd retire that property and just pass the values back as parameters to the completion block.
I've added a little error handling in this.
Your [jsonObject[@"Result"] objectForKey:@"results"]
doesn't look like it could be right. Do you really have JSON that returns a dictionary with a key of "Result" and inside that another dictionary with a key of "results". If so, fine, but that looks suspicious. And even if that was your JSON format, I'd simplify this to say jsonObject[@"Result"][@"results"]
.
Elsewhere it was suggested that you might consider semaphores. That's almost always a bad idea. That's used to make an asynchronous method behave synchronously. But you never want to block the main queue.
Using completion block pattern eliminates the need for semaphores. Apple provides an asynchronous API for a reason, so we should adopt asynchronous patterns when using it.
Upvotes: 2