3

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 ?

user1291401
  • 264
  • 1
  • 6
  • 18
sia
  • 1,872
  • 1
  • 23
  • 54
  • 2
    Please don't, don't, don't. Learn to understand how asynchronous data processing works. You ***don't** need to synchronous call*. There is always an asynchronous way. – vadian Jun 27 '19 at 16:20
  • @vadian - any modification u can just in above code ? – sia Jun 27 '19 at 16:21
  • Yes, remove the semaphore stuff and the horrible `while` loop and replace `dispatch_semaphore_signal(sem); ` with the code which is supposed to run after the data is available. – vadian Jun 27 '19 at 16:23
  • @vadian - replace dispatch_semaphore_signal(sem);- i don't want to continue to other part of code once data is available. Can I put check if(rError != nil) return nil; when there is error. – sia Jun 27 '19 at 16:29
  • That's exactly what asynchronous data processing is about. The code in the `completionHandler` block is executed (much later) when the data **is** available. – vadian Jun 27 '19 at 16:31
  • @vadian - I want it to execute synchronously NSURLSessionDataTask – sia Jun 27 '19 at 16:32
  • 5
    Please read my first comment. Forcing data task to be synchronous is very very bad practice. – vadian Jun 27 '19 at 16:33
  • Bad idea to use synchronous datatasks. Why do you absolutely need to do so? You might want to change architecture, it's usually bad practice. – Larme Jun 27 '19 at 16:52
  • @Larme,@Vadian - I do agree it is bad practice and we do hv plan to re-architecture it, but now to give fix.. using this method. – sia Jun 27 '19 at 17:54

2 Answers2

1

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
}
L33MUR
  • 4,002
  • 2
  • 12
  • 27
  • with completion handler will it be synchronous ? – sia Jul 02 '19 at 11:29
  • When you call startSessionDataTaskWithCompletionHandler, it will execute the completion handler once the dataTask is completed, so you can use the received data – L33MUR Jul 02 '19 at 11:42
1

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]);
}
Roman Podymov
  • 4,168
  • 4
  • 30
  • 57