objective-cdelegatesnsdatansurlsessionnsurlsessionconfiguration

NSURLSession - data is nil


I've got a problem when trying to fetch data with NSURLSession delegate methods. I've come up with this error:

"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'data parameter is nil'"

What I do not understand is that

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {

    CGFloat percentDone = (double)totalBytesWritten/(double)totalBytesExpectedToWrite;
    NSLog(@"Pourcentage: %f", percentDone);
}

is returning 1.000000, and the following delegate method is called

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // Parse the JSON that came in

    NSLog(@"DidComplete"); --> I can see this message

    if (error) {
        NSLog(@"Error in finishLoading: %@", error.localizedDescription); --> but this message is not displayed. There is no error, only data returning nil resulting in a crash...
    }
    else {
    ....
}

But the following methods are not called

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *) response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

    NSLog(@"DidReceiveResponse"); --> not displayed

    completionHandler(NSURLSessionResponseAllow);
}


- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {

    NSLog(@"DidReceiveData. Data: %@", data); --> not displayed

    _downloadedData = [[NSMutableData alloc] init];
    [_downloadedData appendData:data];
}

however it should be called before the didCompleteWithError isn't it? I am assuming the other methods taking care of appending data cannot be called and then data is nil.

I am sure I can fetch the data on the server side since when I am using a block method (and not the delegate methods), the NSData corresponds to what I am looking for.

What is strange is that this error comes after I change the Configuration Session like this:

NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *defaultSession2 = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];

    NSURL *url = [NSURL URLWithString:@"http://localhost:8888/download_list.php"];

    NSURLSessionDownloadTask *dataTask = [defaultSession2 downloadTaskWithURL:url];

When it was like this, I could see the NSData that was retrieved from the server

NSURLSessionDataTask *dataTask = [defaultSession2 dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if(error == nil) {
         NSString * text = [[NSString alloc] initWithData:data encoding: NSUTF8StringEncoding];
         NSLog(@"Data = %@",text); -> text corresponds to the data I am looking for
    }
}];

However it was impossible to call the delegate with a block.

Any idea how the "data" disappears? Or should I change something when not using the block?

Thanks for helping!


Solution

  • The didReceiveResponse and didReceiveData delegate methods will not be called if you call dataTaskWithURL like so:

    NSURLSessionDataTask *dataTask = [defaultSession2 dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        ...
    }];
    

    You're supplying a completion handler, requesting it to capture the response and data for you. So, to do that, it won't call the delegate methods.

    If you want your delegate methods to be called, you should just:

    NSURLSessionDataTask *dataTask = [defaultSession2 dataTaskWithURL:url];
    

    Frankly, though, you're not currently doing anything significant in those methods, so I'd wonder why you want to use the delegate methods rather than the completion handler. The completion handler pattern is simpler.


    By the way, if you're going to use those methods, make sure that didReceiveData only appends. Instantiate the mutable data object in didReceiveResponse. It may take more than one didReceiveData call to receive the entire response, and your original code sample will truncate the results. You presumably want to do something like:

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *) response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
        NSLog(@"DidReceiveResponse");                 // will now be displayed
    
        _downloadedData = [[NSMutableData alloc] init];
    
        completionHandler(NSURLSessionResponseAllow);
    }
    
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
        NSLog(@"DidReceiveData. Data: %@", data);     // will now be displayed
    
        [_downloadedData appendData:data];
    }