N
N
Nikita Kozlov2014-04-17 08:03:21
Objective-C
Nikita Kozlov, 2014-04-17 08:03:21

How to implement a callback to update the UI from an http request (NSURLSession)?

Hello!
I'm writing my first objective-c app for iOS 7.1. I, apparently, do not understand something fundamentally, so this question arose for me.
The application is quite simple: a client for interacting with an external service using some web api. I am using NSURLSession to send http requests.
My architecture is simple.
9a486489cd8f412db223c62a5bbdb51b.png
THETALETestViewController is a view controller. This class uses THETALEAPI.h
THETALEAPI is the web api implementation. I have separated it into a separate class so that each method of the external API is implemented in one place. Here is the formation of a request for each method. This class uses THETALEHttpHandler.h.
THETALEHttpHandler - This class is directly responsible for sending an http request and receiving an http response.
Here is the code from THETALEHttpHandler.m for sending a POST request.

-(void) sendPostToURL:(NSURL *)url withParams: (NSString *) inParams competion:(void (^) (NSData *data)) completion{
    NSLog(@"sendPostToURL");
    
    // _csrftoken некий токен в cookie
    NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary];
    [cookieProperties setObject:@"csrftoken" forKey:NSHTTPCookieName];
    [cookieProperties setObject:_csrftoken forKey:NSHTTPCookieValue];
    
    [cookieProperties setObject:;   
    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:_sessionConfig];
    
    
    NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
    
    // _csrftoken некий токен в параметр
    NSString *params = [@[inParams, _csrftoken] componentsJoinedByString:@""];
    
    [urlRequest setHTTPMethod:@"POST"];
    [urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
   
    NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
                                                       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                        NSLog(@"Got response %@ with error %@.\n", response, error);
                                                           if(error == nil)
                                                           {
                                                               NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
                                                               NSLog(@"Data = %@",text);
                                                           }
                                                       completion(data);
                                                       }];
    [dataTask resume];
    
}

So, the completion block is executed not in the main thread, but asynchronously, so while the request is being sent and the response is being received and processed, the main thread is being executed further. In principle, this is understandable and logical, but I want to "log in" using this method, respectively, by clicking on the "Login" button, I should send a request and after a successful response and its processing, the user should see the answer. What are the iOS features to achieve such behavior so that the main thread waits for the completion of the execution of the asynchronous block and displays the appropriate information? Or maybe he should not wait, but just needs to be notified somehow?
If, of course, you don’t take out separate classes, but do everything in the viewController, then I assume that delegates would be suitable, but I want to bring a little modularity to my application.
I think that this requirement is quite banal and the answer is somewhere nearby, please explain, thanks in advance!

Answer the question

In order to leave comments, you need to log in

[[+comments_count]] answer(s)
K
kaspartus, 2014-04-17
@njc

The completion block doesn't execute anywhere and doesn't go anywhere, it starts executing when the request has been completed one way or another. And the main thread should not wait for anyone. Just at the right time, the session will call the completion block, indeed, not in the main thread.
Some tips and diagram. Perhaps I will not offer an ideal option.
1. We cut everything before creating the session into a separate method, it is logical - we are preparing everything for further work.
2. We write a separate method that creates a task with which we will log in (what is in your method after creating the session). We create the task itself using the dataTaskWithRequest:completionHandler: method , the completion block will be called when the task is completed in one way or another. If success - to item 3
3. We call a separate method inside this block, where we will write further logic after the login, you can add the creation of other tasks there. It is better to call the method in the main thread, because probably want to do something with the UI.

[self performSelectorOnMainThread:@selector(afterLoginLogicMethod) withObject:nil waitUntilDone:NO];

PS Regarding callback from tasks
You can get it through completion blocks, or through delegate methods, whichever is more convenient for you.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question