1

I have successfully implemented the NSURLSessionUploadTask and work in both background and foreground. But there is an issue when reading the response data.

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

    NSLog(@"1 DATA:\n%@\nEND DATA\n", [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]);

    [self.responseData appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (!error) {                        
        NSLog(@"AT THE END DATA:\n%@\nEND DATA\n", [[NSString alloc] initWithData: self.responseData encoding: NSUTF8StringEncoding]);

            [self parsingJSONResponse:self.responseData];

    } else {
        NSLog(@"HTTP uploading error : %@", error);
    }
}

These are the outputs for above two NSLogs

1 DATA: {"success":true,"data":[{"uuid":"8BE7DF37-9DA1-44D2-B48C-D012F699A9B1","id":266626},{"uuid":"3406D865-1A41-4FC6-BA0B-0638F17757CC","id":266656}],"errors":[],"entityName":"LeadProfile"} END DATA

AT THE END DATA: {"success":true,"data":[{"uuid":"8BE7DF37-9DA1-44D2-B48C-D012F699A9B1","id":266626},{"uuid":"3406D865-1A41-4FC6-BA0B-0638F17757CC","id":266656}],"errors":[],"entityName":"LeadProfile"}{"success":true,"data":[{"uuid":"8BE7DF37-9DA1-44D2-B48C-D012F699A9B1","id":266626},{"uuid":"3406D865-1A41-4FC6-BA0B-0638F17757CC","id":266656}],"errors":[],"entityName":"LeadProfile"} END DATA

I wonder why this is giving me two different responses for one upload task. How the self.responseData can be different in each location ?

Is anyone think this is because of the reason mention on Apple website? (Because the NSData object is often pieced together from a number of different data objects, whenever possible, use NSData’s enumerateByteRangesUsingBlock: method to iterate through the data rather than using the bytes method (which flattens the NSData object into a single memory block)developer.apple.com

Chinthaka
  • 966
  • 1
  • 13
  • 42

4 Answers4

2

You ask:

I wonder why this is giving me two different responses for one upload task. How the self.responseData can be different in each location ?

It's undoubtedly because responseData was not instantiated properly or at the right time. (I tend to do it in didReceiveResponse.) Note, you're not looking at responseData in your didReceiveData. You're looking at data. I'd suggest checking responseData immediate after appending the data in didReceiveData, and I'm sure you'll see it doubled there, too. The question is why is it not correctly instantiated/initialized.

Is anyone think this is because of the reason mention on Apple website?

"Because the NSData object is often pieced together from a number of different data objects, whenever possible, use NSData’s enumerateByteRangesUsingBlock: method to iterate through the data rather than using the bytes method (which flattens the NSData object into a single memory block)."

No, this is a completely unrelated issue. I'm sure the issue at play here is far more mundane.

Unfortunately, we don't have enough code here to diagnose the precise problem.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
1

The data you receive in only parts. Your delegate method should concatenate the contents of each data object delivered to build up the complete data for a URL load using a NSMutableData object.

Shams Ahmed
  • 4,498
  • 4
  • 21
  • 27
  • self.responseData is a NSMutableData and I am appending each time it received data. Are you suggesting any other way of using NSMutableData ? – Chinthaka Jul 18 '14 at 08:13
  • 2
    check the task state: task.state == NSURLSessionTaskStateCompleted and make sure countOfBytesExpectedToReceive and countOfBytesReceived are equal. Apart from that it can be related to if the request connection is kept alive or even how your parsing your json... – Shams Ahmed Jul 18 '14 at 09:26
  • BTW, you cannot always rely on `expectedContentLength`. As the documentation for `NSURLResponse` says: "Some protocol implementations report the content length as part of the response, but not all protocols guarantee to deliver that amount of data. Clients should be prepared to deal with more or less data." Worse, sometimes they can't determine content size at all and will report a negative number (e.g. chunked response or gzipped response). Bottom line, one should be very wary of using on the number of bytes received in order to determine completion. – Rob Aug 02 '14 at 03:26
1

Like the documentation says, the received data may be discontiguous.

So, here might be a possible implementation :

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    if (!self.responseData)
    {
        NSUInteger capacity = 0;
        if (dataTask.response.expectedContentLength != NSURLResponseUnknownLength)
        {
            capacity = (NSUInteger)dataTask.response.expectedContentLength;
        }

        self.responseData = [[NSMutableData alloc] initWithCapacity:capacity];
    }

    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        [self.responseData appendBytes:bytes length:byteRange.length];
    }];
}

Your implementation of - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error is correct though.

nverinaud
  • 1,270
  • 14
  • 25
0

I have found a way to overcome this. But this might not be a proper answer.

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

    //Get Hex string from 'data'. There can be a solution directly add bytes to NSData (using 'enumerateByteRangesUsingBlock') rather than convert to Hex string 

    NSMutableString *string = [NSMutableString stringWithCapacity:data.length * 3];
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop){
        for (NSUInteger offset = 0; offset < byteRange.length; ++offset) {
            uint8_t byte = ((const uint8_t *)bytes)[byteRange.location + offset];
            if (string.length == 0)
                [string appendFormat:@"%02X", byte];
            else
                [string appendFormat:@" %02X", byte];
        }
    }];

    //Hex string to NSdata

    NSString *command = string;
    command = [command stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSMutableData *commandToSend= [[NSMutableData alloc] init];
    unsigned char whole_byte;
    char byte_chars[3] = {'\0','\0','\0'};
    for (int i = 0; i < ([command length] / 2); i++) {
        byte_chars[0] = [command characterAtIndex:i*2];
        byte_chars[1] = [command characterAtIndex:i*2+1];
        whole_byte = strtol(byte_chars, NULL, 16);
        [commandToSend appendBytes:&whole_byte length:1];
    }

    NSLog(@"1 >>>>>>>>>>>>>>>>>>> %@", [NSString stringWithUTF8String:[commandToSend bytes]]);

}
Chinthaka
  • 966
  • 1
  • 13
  • 42
  • No, this is certainly not the right solution. In fact, if the `NSData` was actually split into non-contiguous block, this solution wouldn't work (because this was taken from, though not properly attributed to, [another answer](http://stackoverflow.com/a/21489823/1271826) that was incorrect). The index for `bytes` is wrong. That other answer has been fixed. And, fortunately for you, it doesn't split the `NSData` into non-contiguous blocks too often, so you might not have encountered the bug hiding in the above code. This only works because you happen to no longer appending to `responseData`. – Rob Aug 02 '14 at 03:37