returning a value from asynchronous call using semaphores

Shradha

I need to use NSURLSession to make network calls. On the basis of certain things, after I receive the response, I need to return an NSError object.

I am using semaphores to make the asynchronous call behave synchronously. The problem is, the err is set properly inside call, but as soon as semaphore ends (after

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

), the err becomes nil.

Please help

Code:

-(NSError*)loginWithEmail:(NSString*)email Password:(NSString*)password
{
    NSError __block *err = NULL;

        // preparing the URL of login
        NSURL *Url              =       [NSURL URLWithString:urlString];

        NSData *PostData        =       [Post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];

        // preparing the request object
        NSMutableURLRequest *Request = [[NSMutableURLRequest alloc] init];
        [Request setURL:Url];
        [Request setHTTPMethod:@"POST"];
        [Request setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [Request setHTTPBody:PostData];

        NSMutableDictionary __block *parsedData = NULL; // holds the data after it is parsed

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        config.TLSMinimumSupportedProtocol = kTLSProtocol11;

        NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];

        NSURLSessionDataTask *task = [session dataTaskWithRequest:Request completionHandler:^(NSData *data, NSURLResponse *response1, NSError *err){
                if(!data)
                {
                    err = [NSError errorWithDomain:@"Connection Timeout" code:200 userInfo:nil];
                }
                else
                {
                    NSString *formattedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

                    NSLog(@"%@", formattedData);

                    if([formattedData rangeOfString:@"<!DOCTYPE"].location != NSNotFound || [formattedData rangeOfString:@"<html"].location != NSNotFound)
                    {
                        loginSuccessful = NO;
                        //*errorr = [NSError errorWithDomain:@"Server Issue" code:201 userInfo:nil];
                        err = [NSError errorWithDomain:@"Server Issue" code:201 userInfo:nil];
                    }
                    else
                    {
                        parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&err];
                        NSMutableDictionary *dict = [parsedData objectForKey:@"User"];

                        loginSuccessful = YES;
                }
            dispatch_semaphore_signal(semaphore);
        }];
        [task resume];

        // but have the thread wait until the task is done

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return err;
}
Rob

I would suggest cutting the Gordian knot: You should not use semaphores to make an asynchronous method behave synchronously. Adopt asynchronous patterns, e.g. use a completion handler:

- (void)loginWithEmail:(NSString *)email password:(NSString*)password completionHandler:(void (^ __nonnull)(NSDictionary *userDictionary, NSError *error))completionHandler
{
    NSString *post   = ...; // build your `post` here, making sure to percent-escape userid and password if this is x-www-form-urlencoded request
    
    NSURL  *url      = [NSURL URLWithString:urlString];
    NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    // [request setValue:postLength forHTTPHeaderField:@"Content-Length"];                       // not needed to set length ... this is done for you
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];  // but it is best practice to set the `Content-Type`; use whatever `Content-Type` appropriate for your request
    [request setValue:@"text/json" forHTTPHeaderField:@"Accept"];                                // and it's also best practice to also inform server of what sort of response you'll accept
    [request setHTTPBody:postData];
    
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.TLSMinimumSupportedProtocol = kTLSProtocol11;
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
    
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *err) {
        if (!data) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionHandler(nil, [NSError errorWithDomain:@"Connection Timeout" code:200 userInfo:nil]);
            });
        } else {
            NSError *parseError;
            NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&parseError];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                if (parsedData) {
                    NSDictionary *dict = parsedData[@"User"];
                    completionHandler(dict, nil);
                } else {
                    completionHandler(nil, [NSError errorWithDomain:@"Server Issue" code:201 userInfo:nil]);
                }
            });
        }
    }];
    [task resume];
}

And then call it like so:

[self loginWithEmail:userid password:password completionHandler:^(NSDictionary *userDictionary, NSError *error) {
    if (error) {
        // do whatever you want on error here
    } else {
        // successful, use `userDictionary` here
    }
}];

// but don't do anything reliant on successful login here; put it inside the block above

Note:

  1. I know you're going to object to restoring this back to asynchronous method, but it's a really bad idea to make this synchronous. First it's a horrible UX (the app will freeze and the user won't know if it's really doing something or whether it's dead) and if you're on a slow network you can have all sorts of problems (e.g. the watchdog process can kill your app if you do this at the wrong time).

    So, keep this asynchronous. Ideally, show UIActivityIndicatorView before starting asynchronous login, and turn it off in the completionHandler. The completionHandler would also initiate the next step in the process (e.g. performSegueWithIdentifier).

  2. I don't bother testing for HTML content; it is easier to just attempt parse JSON and see if it succeeds or not. You'll also capture a broader array of errors this way.

  3. Personally, I wouldn't return my own error objects. I'd just go ahead and return the error objects the OS gave to me. That way, if the caller had to differentiate between different error codes (e.g. no connection vs server error), you could.

    And if you use your own error codes, I'd suggest not varying the domain. The domain should cover a whole category of errors (e.g. perhaps one custom domain for all of your app's own internal errors), not vary from one error to another. It's not good practice to use the domain field for something like error messages. If you want something more descriptive in your NSError object, put the text of the error message inside the userInfo dictionary.

  4. I might suggest method/variable names to conform to Cocoa naming conventions (e.g. classes start with uppercase letter, variables and method names and parameters start with lowercase letter).

  5. There's no need to set Content-Length (that's done for you), but it is good practice to set Content-Type and Accept (though not necessary).

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Returning response from asynchronous call

From Dev

Returning a value from an asynchronous recursive function

From Dev

Returning value from actor call

From Dev

WCF asynchronous call not returning data

From Dev

WCF asynchronous call not returning data

From Dev

Asynchronous call using xCode

From Dev

Swift: Retrieve value from asynchronous call before view appears

From Dev

Swift: Retrieve value from asynchronous call before view appears

From Dev

Using semaphores to return an image from Parse

From Dev

Using the response from an asynchronous call with the Google Maps API

From Dev

Returning Value From a Method Using java(Threads)

From Dev

Node.js: Returning value from function with async call inside

From Dev

Question about only returning the same value from a method call

From Dev

Node.js: Returning value from function with async call inside

From Dev

Returning values from a function using a switch statement to call the fnctions

From Dev

Returning values from Service asynchronous method to Activity

From Dev

Returning Data from Service to Controller with Asynchronous Callback

From Dev

Function with recursive call not returning the value

From Dev

Node.js Use Promise.all For returning asynchronous call?

From Dev

Using returning value in callback

From Dev

Using Semaphores in Java

From Dev

No output on using Semaphores

From Dev

Process synchronization using semaphores

From Dev

Using Semaphores in Visual Studio

From Dev

Returning a value from a Promise

From Dev

Returning value from setter

From Dev

returning the value from model

From Dev

Returning a value from a Thread?

From Dev

Returning A Value From AlertDialog

Related Related

HotTag

Archive