Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSURLSession background upload not working

I'm trying to upload a series of files from iPhone up to a server, with the intent that the upload of these files happen even when the App is in the background or suspended.

I'm using background transfer provided by NSURLSession and its series of APIs.

what was odd was it it was fully working 2 weeks ago. As in:

  1. I would click the "upload" button on the UI
  2. files would start to appear one by one on my server
  3. I tap the "Home" button on the iPhone to make the app go into the background
  4. Files would continue to upload to the server until it all completes

Last couple of days, I've been doing some refactoring outside of the network module. When I tried the upload again a couple of days ago, as soon as I hit "Home" button, step (3) above. File upload would stop, when I go into the app again. Upload would resume.

It's as if background upload isn't even working (for one file, let alone multiple files).

I've ran the code multiple times, and found about 1 / 50 times it works. But the other 49 times it doesn't. I've also checked out the version of the code that used to work (both server + iOS), and it no longer works - or rather, works vary rarely (the 1/50)

Having gone through the rules for Background transfer and Lifecycle of URL Session multiple times to make sure I'm adhering by Apple suggested guidelines, I'm racking my brain as to what broke, it boggles the mind how illogical this is - I suspect it's something other than code implementation.

So any help is appreciated...

Implementation

1) In init method for my network class (a singleton), I initialise the NSURLSessionConfiguration and NSURLSession:

    urlSessionConfigUpload = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kBACKGROUND_SESSION_CONFIG_ID];
    urlSessionConfigUpload.sessionSendsLaunchEvents = YES;
    urlSessionConfigUpload.discretionary = YES;
    urlSessionConfigUpload.HTTPMaximumConnectionsPerHost = 8;
    urlSessionConfigUpload.networkServiceType = NSURLNetworkServiceTypeBackground;
    urlSessionConfigUpload.HTTPShouldUsePipelining = NO; 
    urlSessionConfigUpload.allowsCellularAccess = NO;

    urlSession = [NSURLSession sessionWithConfiguration:urlSessionConfigUpload delegate:self delegateQueue:nil];

2) There's a convenience method called to do the actual upload. There is only ever 1 upload task per session:

    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    [urlRequest setHTTPMethod:@"PUT"];
    [urlRequest addValue:@"keep-alive" forHTTPHeaderField:@"Connection"];
    [urlRequest addValue:contentType forHTTPHeaderField:@"Content-Type"];

    // NB: for upload task in the background, uploadTaskWithRequest:fromData (explicit construction of HTTP POST body) can’t be used,
    // must use uploadTaskWithRequest:fromFile (requiring HTTP PUT)
    NSURLSessionDataTask *uploadTask = [urlSession uploadTaskWithRequest:urlRequest fromFile:[NSURL fileURLWithPath:filePath]];
    [uploadTask resume];

3) In the didCompleteWithError delegate, I check if all files have been uploaded, if not, move onto the next file - GLOBAL.uploadQueue is where I keep a reference to all files I have to upload, GLOBAL.uploadQueueIndexNextFile

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
        didCompleteWithError:(nullable NSError *)error
{
    if ((error == nil && (GLOBAL.uploadQueueIndexNextFile < GLOBAL.uploadQueue.count - 1)) {
        // Not on last file, increment counter, start upload of next file
        speedLoggerResult = [NSString stringWithFormat:@"Transferring %i of %i files", (GLOBAL.uploadQueueIndexNextFile + 1), GLOBAL.uploadQueue.count];
        GLOBAL.uploadQueueIndexNextFile++; 
        [GLOBAL.fileProcessor processNextFileInUploadQueue];
    }
}

processNextFileInUploadQueue will prepare the file and call the convenience method for upload ((2) above).

Here are some sample output from caveman debugging (for file 2 - 4). Notice, how as soon as app goes into background, upload stops.

Note, I've also waited longer than the 10 seconds shown in the output below. Longest was I left for dinner (30 minutes), came back and the upload ended up timing out. The OS never picked it up once the app is in the background.

2016-02-21 05:53:01 +0000 | bkgd debug - about to start upload task | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 32768 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 65536 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 98304 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 131072 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 163840 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 196608 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 229376 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | totalBytesSent | 233546 of 233546 | queueIndex: 2

2016-02-21 05:53:01 +0000 | in networking | didCompleteWithError | queueindex: 2

bkgd debug - processing next file

2016-02-21 05:53:02 +0000 | bkgd debug - about to start upload task | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 32768 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 65536 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 98304 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 131072 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 163840 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 196608 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 229376 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 262144 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 294912 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 327680 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 360448 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 387704 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 391392 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 393216 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 425984 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 458752 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 491520 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 524288 of 1231286 | queueIndex: 3

2016-02-21 05:53:02 +0000 | in networking | totalBytesSent | 538768 of 1231286 | queueIndex: 3

2016-02-21 05:53:03 +0000 | in networking | totalBytesSent | 541664 of 1231286 | queueIndex: 3

2016-02-21 05:53:03 +0000 | in networking | totalBytesSent | 550352 of 1231286 | queueIndex: 3

2016-02-21 05:53:03 +0000 | in networking | totalBytesSent | 553248 of 1231286 | queueIndex: 3

2016-02-21 05:53:03 +0000 | in networking | totalBytesSent | 557056 of 1231286 | queueIndex: 3

2016-02-21 05:53:03 +0000 | App went into background.

2016-02-21 05:53:03 +0000 | in networking | totalBytesSent | 564832 of 1231286 | queueIndex: 3

2016-02-21 05:53:03 +0000 | in networking | totalBytesSent | 567728 of 1231286 | queueIndex: 3

2016-02-21 05:53:03 +0000 | in networking | totalBytesSent | 582208 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 585104 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 589824 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 621680 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | App came into foreground. 

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 622592 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 655360 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 688128 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 720896 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 753664 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 786432 of 1231286 | queueIndex: 3

2016-02-21 05:53:14 +0000 | in networking | totalBytesSent | 819200 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 851968 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 884736 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 887632 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 893424 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 917504 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 939224 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 950272 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 970544 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 983040 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1015808 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1048576 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1081344 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1114112 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1146880 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1179648 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1212416 of 1231286 | queueIndex: 3

2016-02-21 05:53:15 +0000 | in networking | totalBytesSent | 1231286 of 1231286 | queueIndex: 3

2016-02-21 05:53:16 +0000 | in networking | didCompleteWithError | queueindex: 3

bkgd debug - processing next file

2016-02-21 05:53:16 +0000 | bkgd debug - about to start upload task | queueIndex: 4

2016-02-21 05:53:16 +0000 | in networking | totalBytesSent | 32768 of 1278039 | queueIndex: 4

2016-02-21 05:53:16 +0000 | in networking | totalBytesSent | 65536 of 1278039 | queueIndex: 4

2016-02-21 05:53:16 +0000 | in networking | totalBytesSent | 98304 of 1278039 | queueIndex: 4

2016-02-21 05:53:16 +0000 | in networking | totalBytesSent | 131072 of 1278039 | queueIndex: 4

Happy to try anything at this point. Thanks!

EDIT #1

Observed when background upload does work application:handleEventsForBackgroundURLSession:completionHandler: is called by the OS. Invariably that call back is never made when it doesn't work.

I'm not sure if a prerequisite for background upload is the OS must kill the App first. If so, under what conditions does that happen? And can we prompt it?

As mentioned 49 times out of 50, OS will keep the app in the background, and upload is halted.

like image 481
snowbound Avatar asked Feb 21 '16 09:02

snowbound


People also ask

What is NSURLSession?

The NSURLSession class and related classes provide an API for downloading data from and uploading data to endpoints indicated by URLs. Your app can also use this API to perform background downloads when your app isn't running or, in iOS, while your app is suspended.


1 Answers

There is one thing wanted to make it clear is you cannot keep running in background any task for long, as Apple doesn't allows you. In only special cases Apple consider it. Best explained in Running background services in iOS

Now coming back to your question issue with your implementation is it will only work in background for upload task which are initiated when app was active and still task is not complete. Thats the reason for 1 in 50 attempts you see task is working in background.

Now to solve your issue you have to initiate all/bunch of upload at once so that incase if app goes in background still your app will be able upload files. This briliant tutorial explain different cases related to Background Transfer.

Also you can try out AFNetworking multipart request for upload.

NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
    } error:nil];

AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

NSURLSessionUploadTask *uploadTask;
uploadTask = [manager
              uploadTaskWithStreamedRequest:request
              progress:^(NSProgress * _Nonnull uploadProgress) {
                  // This is not called back on the main queue.
                  // You are responsible for dispatching to the main queue for UI updates
                  dispatch_async(dispatch_get_main_queue(), ^{
                      //Update the progress view
                      [progressView setProgress:uploadProgress.fractionCompleted];
                  });
              }
              completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                  if (error) {
                      NSLog(@"Error: %@", error);
                  } else {
                      NSLog(@"%@ %@", response, responseObject);
                  }
              }];

[uploadTask resume];
like image 186
aman.sood Avatar answered Oct 13 '22 00:10

aman.sood