synchronous NSURLSessionDataTask using objective-c

Viewed 1481

I'm try to do synchronous NSURLSessionDataTask with the below code but unable to proceed.

__block NSData *rData = nil;
  __block BOOL taskDone = NO;
 __block NSData *rError = nil;


    NSURL *url = [NSURL URLWithString:dataURL];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];

    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
    NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        rData = [NSData dataWithData:data];
        rError = [error copy];

        taskDone = YES;
    }];

    [taskData resume];

    while (taskDone == NO) {
        if (_close == YES) {
            [taskData cancel];
            return nil;
        }
        usleep(20000);
    }

I need to synchronous call so that I can remove the while loop which is not needed. Below is my code with synchronous call using semaphore

 dispatch_semaphore_t sem;
   __block NSData *rData = nil;
   __block BOOL taskDone = NO;
  __block NSData *rError = nil;


    NSURL *url = [NSURL URLWithString:dataURL];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];

    // creating semaphore
    sem = dispatch_semaphore_create(0);
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
    NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        rData = [NSData dataWithData:data];
        rError = [error copy];

        taskDone = YES;

        //call semaphore
        dispatch_semaphore_signal(sem); 
    }];

    [taskData resume];
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_release(sema);


   // THIS part not sure...  how can we accommodate this below code
    while (taskDone == NO) {
        if (_close == YES) {
            [taskData cancel];
            return nil;
        }
        usleep(20000);
    }

above code could be correct ?

2 Answers

I understand that what you want to do is wait for the DataTask to be completed before continue with you code, the best way is to put your request in a function with a completionHandler.

First create a function that will return a NSURLSessionDataTask with a completion handler:

-(NSURLSessionDataTask*)startSessionDataTaskWithCompletionHandler:(void (^)(NSData *myData))completionBlock {
    //Set your request
    NSString *dataURL = @"www.yoururl.com";
    NSURL *url = [NSURL URLWithString:dataURL];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];

    // I recommend to use sharedSession because is a simple request, so its not needed a specific session configuration.
    NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest: request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (!error) {
            if (completionBlock){
                completionBlock(data);
                return;
                //When you call this function, the completionBlock will use this data
            }
        } else {
            //Error handle
            return;
        }
    }];
    [dataTask resume];
    return dataTask;
}

Then you can call this function from anywhere:

NSURLSessionTask *task = [self startSessionDataTaskWithCompletionHandler:^(NSData *myData) {
    // put whatever code you want to perform when the asynchronous data task finish, for example:
    rData = [NSData dataWithData:myData];
}];
if (!task) {
    // handle failure to create task any way you want
}

You can make NSURLSessionDataTask synchronous with PromiseKit. Install it manually or add the following line to the Podfile if you use CocoaPods (tested with CocoaPods 1.7.3):

pod "PromiseKit", "6.10.0"

Add the following line to the top of the code file:

@import PromiseKit;

Then create a wrapper for your task:

- (AnyPromise*)promiseToLoadData:(NSString*)dataURL {
    return [AnyPromise promiseWithResolverBlock:^(PMKResolver _Nonnull resolver) {
        NSURL *url = [NSURL URLWithString:dataURL];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
        NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error != nil) {
                resolver([error copy]);
            } else {
                resolver([NSData dataWithData:data]);
            }
        }];
        [taskData resume];
    }];
}

Use wait to resolve the promise synchronously:

id value = [self promiseToLoadData:@"http://your.url"].wait;
if ([value isKindOfClass:[NSData class]]) {
    NSLog(@"%@", [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]);
}
Related