0

I want that NSURLSession's delegation is called on the main thread. When I passed [NSOperationQueue mainQueue] to delegateQueue, the delegation was not called.

    @interface NSURLSessionTests : XCTestCase<NSURLSessionDataDelegate>
    {
        @private
        dispatch_semaphore_t _semaphore;
    }
    @end

    @implementation NSURLSessionTests

    - (void)testWithRequest{
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: @"http://example.com/"]];
        
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
                                                              delegate:self
                                                        delegateQueue:[NSOperationQueue mainQueue]];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
        
        _semaphore = dispatch_semaphore_create(0);
        [dataTask resume];
        
        NSLog(@"Start: %@", dataTask);
        
        dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    }

    # pragma mark NSURLSessionDataDelegate

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                    didReceiveResponse:(NSURLResponse *)response
                                      completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
        NSLog(@"didReceiveResponse: %@", NSThread.currentThread);
        NSLog(@"%@", response);
        completionHandler(NSURLSessionResponseAllow);
    }

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    {
        NSLog(@"didReceiveData: %@", NSThread.currentThread);
        NSLog(@"%@", data);
    }

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
        NSLog(@"didCompleteWithError: %@", NSThread.currentThread);
        NSLog(@"%@", error);
        dispatch_semaphore_signal(_semaphore);
    }
    @end

The executed results are below.

Test Suite 'Selected tests' started at 2020-10-19 16:53:56.013
Test Suite 'Tests.xctest' started at 2020-10-19 16:53:56.014
Test Suite 'NSURLSessionTests' started at 2020-10-19 16:53:56.014
Test Case '-[NSURLSessionTests testWithRequest]' started.
2020-10-19 16:53:56.016936+0900 xctest[91856:6495007] Start: LocalDataTask <A9DFF949-ED27-4C5E-8E15-93113442D876>.<1>

It is freezing here. All the delegation methods aren't called.

if I pass nil to delegateQueue, it worked as expected.


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

but not on the main thread.

Test Suite 'Selected tests' started at 2020-10-19 17:10:03.227
Test Suite 'Tests.xctest' started at 2020-10-19 17:10:03.228
Test Suite 'NSURLSessionTests' started at 2020-10-19 17:10:03.228
Test Case '-[NSURLSessionTests testWithRequest]' started.
2020-10-19 17:10:03.230845+0900 xctest[91910:6506707] Start: LocalDataTask <44C2D9FA-09D3-4C25-9129-B164D451836D>.<1>
2020-10-19 17:10:03.629414+0900 xctest[91910:6506750] didReceiveResponse: <NSThread: 0x7f8d2281f480>{number = 2, name = (null)}
2020-10-19 17:10:03.629867+0900 xctest[91910:6506750] <NSHTTPURLResponse: 0x7f8d20f333e0> { URL: http://example.com/ } { Status Code: 200, Headers {
    ...
} }
2020-10-19 17:10:03.630215+0900 xctest[91910:6506750] didReceiveData: <NSThread: 0x7f8d2281f480>{number = 2, name = (null)}
2020-10-19 17:10:03.630459+0900 xctest[91910:6506750] #NSData 1KB(1256)
2020-10-19 17:10:03.630940+0900 xctest[91910:6506750] didCompleteWithError: <NSThread: 0x7f8d2281f480>{number = 2, name = (null)}
2020-10-19 17:10:03.631095+0900 xctest[91910:6506750] (null)
Test Case '-[NSURLSessionTests testWithRequest]' passed (0.403 seconds).
Test Suite 'NSURLSessionTests' passed at 2020-10-19 17:10:03.632.
  Executed 1 test, with 0 failures (0 unexpected) in 0.403 (0.404) seconds
Test Suite 'Tests.xctest' passed at 2020-10-19 17:10:03.633.
  Executed 1 test, with 0 failures (0 unexpected) in 0.403 (0.405) seconds
Test Suite 'Selected tests' passed at 2020-10-19 17:10:03.633.
  Executed 1 test, with 0 failures (0 unexpected) in 0.403 (0.406) seconds
Program ended with exit code: 0

Why isn't the delegation called with [NSOperationQueue mainQueue]? How do I work on the main thread?

2020/10/23 Edited

As commented, the Xctest code worked when I changed to XCTestExpectation instead of dispatch_semaphore_t.

@interface NSURLSessionTests : XCTestCase<NSURLSessionDataDelegate>
{
    @private
    XCTestExpectation *_expection;
}
@end

@implementation NSURLSessionTests
- (void)testWithRequest{
    _expection = [self expectationWithDescription:@"UIDocument is opened."];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: @"http://example.com/"]];
    
...
    
    [dataTask resume];
    [self waitForExpectations:[NSArray arrayWithObject:_expection] timeout:12.0];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSLog(@"didCompleteWithError: %@", NSThread.currentThread);
    NSLog(@"%@", error);
    [_expection fulfill];
}
@end

But my app code doesn't still work. When I checked the app code, I found processing waiting for the end of HTTP request.

- (void)startSync
{
    FcLicenseChecker *license = [FcLicenseChecker sharedInstance];
    if(!license.isCheckRunning)
    {
        [license execute];
    }
    
    [license waitUntilCheckFinished];
...
@implementation FcLicenseChecker

- (void)waitUntilCheckFinished
{
    while (_isCheckRunning)
    {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
  ...
  _isCheckRunning = NO;
}

It was doing roop in waitUntilCheckFinished without the delegation called.

I think that the way using [NSRunLoop currentRunLoop] runUntilDate for waiting for the end worked with NSURLConnection's delegation, but not with NSURLSession.

I think that it will work to change to fire the next process in didCompleteWithError instead of waiting for the end, but it is very hard because there are many codes using this class.

Is there a way to wait for the end with NSURLSession?

Masamoto Miyata
  • 171
  • 4
  • 9
  • I struggle to understand your code, but I think I understand the question. Why don't you just switch to the main thread when you need to using ```dispatch_async ( dispatch_get_main_queue(), ^ { ...stuf you want to do on main... } );``` – skaak Oct 19 '20 at 08:57
  • 1
    Looks like you're trying to do asynchronous unit testing. Take a look at this Apple doc that shows how to do this correctly with `XCTestExpectation`: [Testing Asynchronous Operations](https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations?language=objc). Their example is only written in Swift, but you should be able to translate it to Objective-C easily. – TylerP Oct 19 '20 at 09:09
  • @Tyler I thought it was the same as `dispatch_semaphore_wait` but I tried what you said, it worked! However, my problem isn't solved yet. It works on XCTest, but not on my app. I think the problem is about the multithread issue. I will try more. Anyway thanks. – Masamoto Miyata Oct 20 '20 at 04:14
  • @skaak As you said, when I used `dispatch_async` in the delegation method, it worked on the main thread, but it's only on XCTest, not on my app. On the app, the `dispatch_async` block wasn't called. I don't know why. – Masamoto Miyata Oct 20 '20 at 04:25
  • 1
    Ok - that *should* have been called on the main definitely - can you post your new code, just the delegate and where you call it. BTW why do you use a semaphore? Im asking as it could be the perfect solution here or it could be very wrong so I just want some context to comment on that as well. – skaak Oct 20 '20 at 04:37

0 Answers0