Why does it have retain cycle here? - ios

Here is the code from Ray tutorial about ReactiveCocoa and I can't figure out how come it has retain cycle, can someone indicate that?
- (RACSignal *)signalForSearchWithText:(NSString *)text {
// 1 - define the errors
NSError *noAccountsError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorNoTwitterAccounts
userInfo:nil];
NSError *invalidResponseError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorInvalidResponse
userInfo:nil];
// 2 - create the signal block
#weakify(self)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
#strongify(self);
// 3 - create the request
SLRequest *request = [self requestforTwitterSearchWithText:text];
// 4 - supply a twitter account
NSArray *twitterAccounts = [self.accountStore
accountsWithAccountType:self.twitterAccountType];
if (twitterAccounts.count == 0) {
[subscriber sendError:noAccountsError];
} else {
[request setAccount:[twitterAccounts lastObject]];
// 5 - perform the request
[request performRequestWithHandler: ^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (urlResponse.statusCode == 200) {
// 6 - on success, parse the response
NSDictionary *timelineData =
[NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:nil];
[subscriber sendNext:timelineData];
[subscriber sendCompleted];
}
else {
// 7 - send an error on failure
[subscriber sendError:invalidResponseError];
}
}];
}
return nil;
}];
}
Because I am trying to remove #weakify(self) and #stringify(self)

There isn't an obvious retain cycle in this code. You ought to be able to remove the #weakify/#strongify usages without a problem.

In block , u shouldn't use self directly.
self keeps a block instance and the block keeps self. U can use a weak self in block to avoid the retain cycle.

Related

Asynchronous call using xCode

I have another very beginner's question related to xCode. I am completely new to iOS development so I appreciate you guys to reply me.
I have written the following class to access the Restful API. The code in the method "makePostRequest" works fine if I write it directly in the calling method. But, I want to make it asynchronous and I don't know exactly how can I make this work asynchronous. Can somebody help me please to write this as asynchronos call?
#import <Foundation/Foundation.h>
#import "ServerRequest.h"
#import "NetworkHelper.h"
#implementation ServerRequest
#synthesize authorizationRequest=_authorizationRequest;
#synthesize responseContent=_responseContent;
#synthesize errorContent=_errorContent;
#synthesize url=_url;
#synthesize urlPart=_urlPart;
#synthesize token=_token;
- (void)makePostRequest : (NSString *) params {
NSString *urlString = [NSString stringWithFormat:#"%#%#", [self getUrl], [self getUrlPart]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
if([self isAuthorizationRequest]) {
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"Basic" forHTTPHeaderField:#"Authorization"];
}
else {
NSString *authorizationValue = [NSString stringWithFormat:#"Bearer %#", [self getToken]];
[request setValue:authorizationValue forHTTPHeaderField:#"Authorization"];
}
if(params.length > 0)
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
#try {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
NSLog(#"Error: %#", error);
}
if([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if(statusCode == [NetworkHelper HTTP_STATUS_CODE]) {
self.responseContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:nil];
}
else {
self.errorContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:nil];
}
}
}];
[dataTask resume];
}
#catch (NSException *exception) {
NSLog(#"Exception while making request: %#", exception);
} #finally {
NSLog(#"finally block here");
}
}
- (void)setAuthorization : (bool)value {
self.authorizationRequest = &value;
}
- (bool)isAuthorizationRequest {
return self.authorizationRequest;
}
- (NSDictionary *)getResponseContent {
return self.responseContent;
}
- (NSDictionary *)getErrorContent {
return self.errorContent;
}
- (void)setToken:(NSString *)token {
self.token = token;
}
- (NSString *)getToken {
return self.token;
}
- (void)setUrl:(NSString *)value {
//self.url = value;
_url = value;
}
- (NSString *)getUrl {
return self.url;
}
- (void)setUrlPart:(NSString *)value {
self.urlPart = value;
}
- (NSString *)getUrlPart {
if(self.urlPart.length == 0)
return #"";
return self.urlPart;
}
#end
I'm giving you an example how you can make your method serve you data when available. It's block based. So you don't have to consider asynchronous task here.
First define your completion block in your ServerRequest.h:
typedef void(^myCompletion)(NSDictionary*, NSError*);
And change your method's signature to this:
- (void) makePostRequest:(NSString *)params completion: (myCompletion)completionBlock;
Now change your method's implementation to something like this (I'm only posting your #try block, so just change your try block. Others remain same)
#try {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
NSLog(#"Error: %#", error);
if (completionBlock) {
completionBlock(nil, error);
}
}
if([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if(statusCode == [NetworkHelper HTTP_STATUS_CODE]) {
NSError *error;
self.responseContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
if (completionBlock) {
if (error == nil) {
completionBlock(self.responseContent, nil);
} else {
completionBlock(nil, error);
}
}
} else {
NSError *error;
self.errorContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
if (completionBlock) {
if (error == nil) {
completionBlock(self.errorContent, nil);
} else {
completionBlock(nil, error);
}
}
}
}
}];
[dataTask resume];
}
Finally, when you call this method from somewhere else, use this as:
[serverRequestObject makePostRequest:#"your string" completion:^(NSDictionary *dictionary, NSError *error) {
// when your data is available after NSURLSessionDataTask's job, you will get your data here
if (error != nil) {
// Handle your error
} else {
// Use your dictionary here
}
}];

Execute task after another

Recently I started developing for iOS and faced problem which is maybe obvious for you but I couldn't figure it out by myself.
What I'm trying to do is to execute task after another one, using multithreading provided by GCD.
This is my code for fetching JSON (put in class with singleton)
CategoriesStore
- (instancetype)initPrivate {
self = [super init];
if (self) {
[self sessionConf];
NSURLSessionDataTask *getCategories =
[self.session dataTaskWithURL:categoriesURL
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error) {
NSLog(#"error - %#",error.localizedDescription);
}
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *) response;
if (httpResp.statusCode == 200) {
NSError *jsonError;
NSArray *json =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&jsonError];
if (!jsonError) {
_allCategories = json;
NSLog(#"allcategories - %#",_allCategories);
}
}
}];
[getCategories resume];
}
return self;
}
Then in ViewController I execute
- (void)fetchCategories {
NSLog(#"before");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
CategoriesStore *categories = [CategoriesStore sharedStore];
dispatch_async(dispatch_get_main_queue(), ^(void) {
_allDirectories = categories.allCategories;
[self.tableView reloadData];
NSLog(#"after");
});
});
}
-fetchCategories is executed in viewDidAppear. The result is usually before, after and then JSON. Obviously what I want to get is before, json after.
I also tried to do this with dispatch_group_notify but didn't workd.
How can I get it working? Why it doesn't wait for first task to be finished?
Thank's for any help!
Regards, Adrian.
I would suggest to define a dedicated method in CategoriesStore that fetches data from remote server and takes callback as an argument:
- (void)fetchDataWithCallback:(void(^)(NSArray *allCategories, NSError* error))callback
{
NSURLSessionDataTask *getCategories =
[self.session dataTaskWithURL:categoriesURL
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error) {
NSLog(#"error - %#",error.localizedDescription);
callback(nil, error);
return;
}
NSError *jsonError = nil;
NSArray *json =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&jsonError];
if (!jsonError) {
_allCategories = json;
NSLog(#"allcategories - %#",_allCategories);
callback(_allCategories, nil);
} else {
callback(nil, jsonError);
}
}];
[getCategories resume];
}
And you can use it in your ViewController:
- (void)fetchCategories {
[[CategoriesStore sharedStore] fetchDataWithCallback:^(NSArray *allCategories, NSError* error) {
if (error) {
// handle error here
} else {
_allDirectories = allCategories;
[self.tableView reloadData];
}
}]
}
In this way you will reload your table view after data loading & parsing.
You have to wait for the reload data so you may do something like this, another option if you don't want to wait for the whole block and just for the fetch is to use a custom NSLock
dispatch_sync(dispatch_get_main_queue(), {
_allDirectories = categories.allCategories;
[self.tableView reloadData];
}
NSLog(#"after");
I used method suggested by #sgl0v, although it wasn't solution I expected.
Another way to do this is by using notification center and listening for event to occur.

RACDisposable is teared down before NSURLSessionDownloadTask finishes

I am fetching a list of images that I want to download from a server. After filtering the list , i add all my network calls to an array of signals and merge them. The thing is that the network calls start, but meanwhile the racdisposable is destroyed and in the destroy method I call cancel on the download task. Calling cancel stops the request and the image is not pulled. Any suggestions on how I should do this? I am pretty new to IOS and ReactiveCocoa
This method starts the requests
- (void)fetchTagsFromServer{
[self CreateIfNotExistsTagsFolder];
//define URL object
NSString *urlString = [NSString stringWithFormat:#"http://studiotest.cloudapp.net:5508/api/sideviewapi/gettags"];
NSURL *url = [NSURL URLWithString:urlString];
//fetch tag list from server. After the json is here, start filtering and pulling the images
//from the server
[[self fetchJSONFromURL:url] subscribeNext:^(id x){
NSDictionary* json = (NSDictionary*)x;
//filter and pull images
[[self handleTagsFromJson:json] subscribeNext:^(id x) {
initTagsDone = false;
} error:^(NSError *error) {
NSLog([error description]);
}] ;
}];
}
This ia a generic method to pull json from server
- (RACSignal *)fetchJSONFromURL:(NSURL *)url {
NSLog(#"Fetching: %#",url.absoluteString);
// RAC signal
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//Create session task
NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (! error) {
NSError *jsonError = nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
if (! jsonError) {
//if json returned with success sent to subscriber
[subscriber sendNext:json];
}
else {
// send json error to subscriber
[subscriber sendError:jsonError];
}
}
else {
// send general error
[subscriber sendError:error];
}
// send request completed
[subscriber sendCompleted];
}];
//Starts the the network request once someone subscribes to the signal.
[dataTask resume];
// Creates and returns an RACDisposable object which handles any cleanup when the signal when it is destroyed.
return [RACDisposable disposableWithBlock:^{
[dataTask cancel];
}];
}] doError:^(NSError *error) {
// log error
NSLog(#"%#",error);
}] ;
}
Here I combine all the signals
(RACSignal *) handleTagsFromJson:(NSDictionary*) json {
//hold tags dto's
NSMutableArray* tags = [[NSMutableArray alloc] init];
NSMutableArray* signals = [[NSMutableArray alloc]init];
//create dto object from json
for (NSDictionary * item in json) {
TagsDTO* dto = [[TagsDTO alloc]init];
dto.Id = [[item valueForKey:#"Id"] intValue];
dto.BigIcon = [item valueForKey:#"BigIcon"];
dto.SmallIcon = [item valueForKey:#"SmallIcon"];
dto.SmallIconRaster = [item valueForKey:#"SmallIconRaster"];
dto.Date = [TagsDTO dateWithJSONString:[item valueForKey:#"Modified"]];
dto.Type = [[item valueForKey:#"Type"] intValue];
[tags addObject:dto];
}
//foreach dto do stuff with db
for (TagsDTO* tagDTO in tags){
//get DB tag by external ID
TAG* dbTag = [self getTagsByExternalID:tagDTO.Id];
//create holder strings for image names
NSString* pickerTag;
NSString* overlay;
NSString* raster;
//if db tag is null create null,else set values from server
if(dbTag == NULL){
TAG* tag = [NSEntityDescription insertNewObjectForEntityForName:#"TAG" inManagedObjectContext:self.managedObjectContext];
tag.externalId = [NSNumber numberWithInt:tagDTO.Id];
tag.pickerITag = pickerTag = tagDTO.BigIcon;
tag.overlayTag = overlay = tagDTO.SmallIcon;
tag.rasterTag = raster = tagDTO.SmallIconRaster;
tag.modified = tagDTO.Date;
tag.type = [NSNumber numberWithInt:tagDTO.Type];
}else{
dbTag.externalId = [NSNumber numberWithInt:tagDTO.Id];
dbTag.pickerITag = pickerTag = tagDTO.BigIcon;
dbTag.overlayTag = overlay = tagDTO.SmallIcon;
dbTag.rasterTag = raster = tagDTO.SmallIconRaster;
dbTag.modified = tagDTO.Date;
dbTag.type = [NSNumber numberWithInt:tagDTO.Type];
}
NSError *error ;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
//start downloading images
//because there are 3 different types for each images, download from coressponding serveer folder
//get picker picture
[signals addObject: [self fetchTagImage:pickerTag area:#"picker"]];
//get overlay picture
[signals addObject: [self fetchTagImage:pickerTag area:#"overlay"]];
//get raster picture
[signals addObject:[self fetchTagImage:pickerTag area:#"raster"]];
}
return [RACSignal combineLatest:signals];
}
And this is the signal for pulling the images from the server
(RACSignal *) fetchTagImage:(NSString*)tag area: (NSString*) area {
//create Url object, area is the folder on the server
NSString *urlStringIcon = [NSString stringWithFormat:#"http://studiotest.cloudapp.net:5508/content/uploads/%#/%#",area,tag];
NSURL *urlIcon = [NSURL URLWithString:urlStringIcon];
NSLog(#"Fetching: %#",urlIcon.absoluteString);
// RAC signal
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//Create session task
NSLog(#"internal image fetch: %#",urlIcon.absoluteString);
NSURLSessionDownloadTask* dataTask = [self.session downloadTaskWithURL:urlIcon completionHandler:^(NSURL*location, NSURLResponse *response, NSError *error) {
if(error == nil || error.code == NSURLErrorCancelled)
{
NSLog(#"Temporary file =%#",location);
NSError *err = nil;
//create file mananger
NSFileManager *fileManager = [NSFileManager defaultManager];
//get app document folder path
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//create actual store path
NSString *dataPath = [docsDir stringByAppendingPathComponent:#"/tags"];
//compile filename
NSString* filename = [NSString stringWithFormat:#"%#-%#",area,[response suggestedFilename]];
//create URL to move temporary item to
NSURL *docsDirURL = [NSURL fileURLWithPath:[dataPath stringByAppendingPathComponent:filename]];
if ([fileManager moveItemAtURL:location
toURL:docsDirURL
error: &err])
{
[subscriber sendNext:filename];
NSLog(#"File is saved to =%#",docsDir);
}
else
{
[subscriber sendError:err];
NSLog(#"failed to move: %#",[err userInfo]);
}
}else {
// send general error
[subscriber sendError:error];
}
// send request completed
[subscriber sendCompleted];
}];
//Starts the the network request once someone subscribes to the signal.
[dataTask resume];
// Creates and returns an RACDisposable object which handles any cleanup when the signal when it is destroyed.
return [RACDisposable disposableWithBlock:^{
[dataTask cancel];
}];
}] doError:^(NSError *error) {
// log error
NSLog(#"%#",error);
}];
}
Let's check a couple things first:
racdisposable is destroyed
It's OK for a RACDisposable to be deallocated. Note that that doesn't call the disposable's dispose block.
I don't see any obvious reason that the disposable would be disposed of. Is the signal erroring? Could you set a breakpoint and see the stack trace that calls the cancel?

Understanding RACSignal Error

I'm currently taking my first steps in ReactiveCocoa and I experience some steep learning curve to understand the principals.
Anyway here's what I already came up with.
I bind a NSArray property to a RACSignal to be able to react to the incoming JSON data over network.
- (void)updateRandomUserData
{
#weakify(self);
RAC(self, users) = [[self fetchRandomUserData] doNext:^(NSDictionary *json){
#strongify(self);
NSMutableArray *randomUsers = [NSMutableArray array];
for (NSDictionary *dict in json[#"data"]) {
BKRandomUser *randomUser = [MTLJSONAdapter modelOfClass:[BKRandomUser class] fromJSONDictionary:dict error:nil];
[randomUsers addObject:randomUser];
}
self.users = randomUsers;
}];
}
The signal creation looks like this:
- (RACSignal *)fetchRandomUserData
{
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[subscriber sendNext:JSON];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON, NSError *error) {
[subscriber sendError:error];
}];
[operation start];
return [RACDisposable disposableWithBlock:^{
[operation cancel];
}];
}] doError:^(NSError *error) {
NSLog(#"error: %#", [error description]);
}];
}
Now when the web service doesn't provide any data I want to react to this. Right now the app crashes with the following statement, which I honestly don't understand:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Received error from name: [[+createSignal:] -doError:] -doNext: in binding for key path "users" on : (null)'
What am I missing here?
Thank you!
The accepted answer will work around the problem, but there's a more idiomatic, elegant way to handle this:
First off, instead of using -doNext:, use -map: to transform your JSON into the array of users:
RAC(self, users) = [[self fetchRandomUserData] map:^(NSDictionary *json){
NSMutableArray *randomUsers = [NSMutableArray array];
for (NSDictionary *dict in json[#"data"]) {
BKRandomUser *randomUser = [MTLJSONAdapter modelOfClass:[BKRandomUser class] fromJSONDictionary:dict error:nil];
[randomUsers addObject:randomUser];
}
return randomUsers;
}];
Then, to handle the error, you can use -catch::
RAC(self, users) = [[[self fetchRandomUserData] map:^(NSDictionary *json){
NSMutableArray *randomUsers = [NSMutableArray array];
for (NSDictionary *dict in json[#"data"]) {
BKRandomUser *randomUser = [MTLJSONAdapter modelOfClass:[BKRandomUser class] fromJSONDictionary:dict error:nil];
[randomUsers addObject:randomUser];
}
return randomUsers;
}] catch:^(NSError *error) {
return [RACSignal return:#[]];
}];
In this example, if an error happens we catch it and replace it with an empty array. You could do whatever you wanted there. Replace it with nil, or +[RACSignal empty] if you just want to ignore the whole thing. Or call another method that returns a RACSignal *.
By writing RAC(self, users), you means you want to binding the signal's return to the property users, but you are not returning anything in the signal handler.
You are using the doNext, which is a injection for current signal. You may want to subscribe it instead of injecting the side-effect.
Solve:
You can remove the RAC(self, users) binding if you want to assign it in the subscribeNext side-effect, so just subscribe for the signal would be OK for your case, like this:
#weakify(self);
[[self fetchRandomUserData] subscribeNext:^(NSDictionary *json) {
#strongify(self);
NSMutableArray *randomUsers = [NSMutableArray array];
for (NSDictionary *dict in json[#"data"]) {
BKRandomUser *randomUser = [MTLJSONAdapter modelOfClass:[BKRandomUser class] fromJSONDictionary:dict error:nil];
[randomUsers addObject:randomUser];
}
self.users = randomUsers;
}];

Running NSURLSession completion handler on main thread

I am using a NSURLSession to get the values to populate a TableView. I am updating the TableView in the completion handler, but using [[NSThread currentThread] isMainThread] has shown me that the completion handler isn't running in the main thread. Since I should only updating the UI from the main thread, I know this isn't correct. Is there a way to trigger an action on the main thread from the completion handler? Or is using a NSURLSession the wrong way to go about this?
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://myurl"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError = nil;
NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(#"error is %#", [jsonError localizedDescription]);
// Handle Error and return
return;
}
self.userArray = jsonUsers;
[self.userTableView reloadData];
if ([[NSThread currentThread] isMainThread]){
NSLog(#"In main thread--completion handler");
}
else{
NSLog(#"Not in main thread--completion handler");
}
}] resume];
Yes, just dispatch your main thread stuff using GCD:
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://myurl"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError = nil;
NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(#"error is %#", [jsonError localizedDescription]);
// Handle Error and return
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.userArray = jsonUsers;
[self.userTableView reloadData];
if ([[NSThread currentThread] isMainThread]){
NSLog(#"In main thread--completion handler");
}
else{
NSLog(#"Not in main thread--completion handler");
}
});
}] resume];
#graver's answer is good. Here's another way you can do it:
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://myurl"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError = nil;
NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(#"error is %#", [jsonError localizedDescription]);
// Handle Error and return
return;
}
self.userArray = jsonUsers;
[self.userTableView reloadData];
if ([[NSThread currentThread] isMainThread]){
NSLog(#"In main thread--completion handler");
}
else{
NSLog(#"Not in main thread--completion handler");
}
}] resume];
This way you create a session that calls the completion block and any delegate methods on the main thread. You may find this more aesthetically pleasing, but you do lose the advantage of running the "hard work" in the background.
Here is the best way to update UI from blocks and completion handler, and also when you not confrim which thread running your code.
static void runOnMainThread(void (^block)(void))
{
if (!block) return;
if ( [[NSThread currentThread] isMainThread] ) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
This is static method which will have a block, and will run on main thread, it will act like a
runOnMainThread(^{
// do things here, it will run on main thread, like updating UI
});
You can try this:
[self.userTableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
send a notification on completion that will be observed by the table view controller which will then do a reloadData;
this has the advantage that if you later move this download code off to a separate class, e.g. a model object, then no changes are required and also the code can be reused in other projects without making changes
Swift 3.1
DispatchQueue.main.async {
tableView.reloadData()
}

Resources