问题
To say I'm new to Objective-C would be a huge understatement. I'm primarily a Ruby/Rails developer and it completely spoiled me when it comes to OOP & programming in general.
After getting tired of reading tutorials, I decided to try to use NSRULSession to hit one of my Rails apps (an Elder Scrolls Online skill planner) & display some of the JSON response on my iOS app. Delegates make no sense, I'm not sure how to break this functionality up into methods, etc, so I thought I'd keep it simple & do it all in the viewDidLoad() method (yes, I know it's bad practice).
- (void)viewDidLoad {
[super viewDidLoad];
__block NSDictionary *skillData; // No clue what __block is
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"http://www.esomix.com/skill_builds/17.json"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", json[@"name"]);
skillData = json;
}];
[dataTask resume];
UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 100, 200, 100)];
myLabel.textColor = [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1];
[self.view addSubview:myLabel];
NSLog(@"%@", skillData[@"name"]);
NSLog(@"bottom of method");
}
After lots of toying around, I figured out that despite the NSURLSession code before my NSLogs at the bottom, it returns its data after they're rendered. No wonder my label.text (not shown) wasn't getting set! Here's the order of my three NSLogs:
(null)
bottom of method
Single-Target Lockdown Sniper
I guess my question is what's the simplest, proper way to make an JSON API request and use the data to generate a UI element/other tasks after the data has returned. Thanks so much!
回答1:
A couple of clarifying points:
You ask
what's the simplest, proper way to make an JSON API request and use the data to generate a UI element/other tasks after the data has returned
In short, use the JSON response inside the completion block, not after it, for example:
- (void)viewDidLoad { [super viewDidLoad]; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"http://www.esomix.com/skill_builds/17.json"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { NSDictionary *skillData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; // use `skillData` here // finally, any UI/model updates should happen on main queue dispatch_async(dispatch_get_main_queue(), ^{ UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 100, 200, 100)]; myLabel.textColor = [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1]; myLabel.text = skillData[@"name"]; // or whatever you wanted from `skillData` [self.view addSubview:myLabel]; }); }]; [dataTask resume]; // don't try to use `skillData` here, as the above block runs asynchronously, // and thus `skillData` will not have been set yet }
The purpose of the
__block
qualifier is to let the block update a variable whose scope is outside the block. But, because theNSURLSessionDataTask
runs asynchronously, there's no point in trying to referenceskillData
outside that block (because theviewDidLoad
method will have completed well before thecompletionHandler
for theNSURLSessionDataTask
is invoked, as illustrated by yourNSLog
results).So, since there's no point in referencing
skillData
outside the block, then there's no point in defining it with the__block
qualifier outside of the block. Just make it a local variable inside the block. If you want to update your model (or perhaps some properties of the view controller), you can do that (but when dealing with class properties and ivars, no__block
qualifier is needed).
来源:https://stackoverflow.com/questions/20917297/how-to-assign-result-of-nsurlsession-to-a-variable-in-same-method