9

I'm trying to upload large files using AFNetworking and have the upload continue when the application is in the background.

I can upload files just fine, but when I attempt to use a background configuration -- the application crashes with the following stacktrace: Exception: EXC_BAD_ACCESS (code=1, address=0x8000001f))

_CFStreamSetDispatchQueue
-[__NSCFBackgroundDataTask captureStream:]
__70-[__NSCFBackgroundDataTask _onqueue_needNewBodyStream:withCompletion:]_block_invoke_2
_dispatch_call_block_and_release
_dispatch_queue_drain
_dispatch_queue_invoke
_dispatch_root_queue_drain
_dispatch_worker_thread3
_pthread_wqthread

Here is some example code:

Note: When I use [NSURLSessionConfiguration defaultSessionConfiguration] the upload succeeds but will not continue when the application is in the background. [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.company.appname.uploadservice"] causes the application to crash.

NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:[uploadBundle.uploadUrl absoluteString] parameters:[uploadBundle getParameters] constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
    [formData appendPartWithFileURL:uploadBundle.fileDataUrl name:uploadBundle.fileName fileName:uploadBundle.fileName mimeType:uploadBundle.mimeType error:nil];
} error:nil];

Authentication *authentication = [Authentication getInstance];
[request addValue:authentication.token forHTTPHeaderField:@"token"];
[request addValue:authentication.authorization forHTTPHeaderField:@"Authorization"];

//NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.company.appname.uploadservice"];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSProgress *progress = nil;
_currentUploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
    if (error) {
        NSLog(@"Error: %@", error);
    } else {
        NSLog(@"%@ %@", response, responseObject);
    }
}];
benbarth
  • 286
  • 3
  • 12

2 Answers2

8

I'm answering my own question in hopes that it will help a few people.

I can't find this documented anywhere, but it looks like you have to use uploadTaskWithRequest:fromFile:progress:completionHandler: when using a background session configuration.

Here is a simple example:

AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];

id config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.opentext.core.uploadservice"];
id session = [NSURLSession sessionWithConfiguration:config delegate:appDelegate delegateQueue:[NSOperationQueue new]];

OMGMultipartFormData *multipartFormData = [OMGMultipartFormData new];
//[multipartFormData addFile:data parameterName:@"file" filename:nil contentType:nil];
[multipartFormData addParameters:[uploadBundle getParameters]];

NSURLRequest *rq = [OMGHTTPURLRQ POST:[uploadBundle.uploadUrl absoluteString] :multipartFormData];

id path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"upload.NSData"];
[rq.HTTPBody writeToFile:path atomically:YES];

[[session uploadTaskWithRequest:rq fromFile:[NSURL fileURLWithPath:path]] resume];
benbarth
  • 286
  • 3
  • 12
  • I also have same problem you raised above. In my case I need to upload multiple files in single request. This way is that possible to attach multiple files to a single request? – Inoka Dec 11 '14 at 06:42
  • Can you share OMGMultipartFormData Files i have tried to find it But Didn't Got it. – Devil May 13 '15 at 12:54
  • 4
    this answer is quite unclear. What is `uploadBundle`? And what are you trying to do with the line `id path` ? – Raptor Jul 02 '15 at 09:36
  • what is OMGMultipartFormData – Prashant Tukadiya Jun 06 '16 at 13:24
  • Please explain "OMGMultipartFormData ". – Ekta Padaliya Jun 15 '16 at 11:55
  • @Raptor It's looks like `uploadBundle` is a some sort of structure that holds networking info (such as urls, paths, etc.). – sig Jun 21 '16 at 14:24
  • @MikeAlter @EktaMakadiya Take a look at AFNetworking method called `multipartFormRequestWithMethod(URLString: parameters: constructingBodyWithBlock: error:)`. It uses constructing block with `AFMultipartFormData` instance as a parameter that you should fill with data (such as image or JSON). Simple example in swift: `formData.appendPartWithFileData(UIImageJPEGRepresentation(myImage, 1)!, name: "file", fileName: "image.jpg", mimeType: "image/jpg")`. – sig Jun 21 '16 at 14:31
  • Like a few others have mentioned his answer does not help with knowledge of OMGMulitpartFormData. Can you please update the answer and explain OMGMultipartFormData or remove it in favor of an AFNetowtking class is iOS class? – lostintranslation Jun 02 '17 at 01:56
0

The issues seems to be that you are trying to use a "streamed request" instead of file. The code works with

NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:filePath] progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error){

.... }];

method without any issues. I also found that if you try to use the file data instead of the actual file (with uploadTaskWithRequest: fromData: progress: completionHandler:), the upload fails as explained in Upload tasks from NSData are not supported in background sessions

Community
  • 1
  • 1
tuttu47
  • 521
  • 1
  • 4
  • 19
  • So you can only upload one file at a time? No multipartFormData equivalent? – shim Oct 28 '15 at 21:54
  • 3
    You can upload multipart data, just that you need to save the multipart data to a file and then upload the file. Then, [serializer requestWithMultipartFormRequest:request writingStreamContentsToFile:filePathtemp completionHandler:^(NSError *error) { NSURLSessionUploadTask *uploadTask = [self.activeSessionManager uploadTaskWithRequest:request fromFile:filePathtemp progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { }]; [uploadTask resume]; }]; – tuttu47 Oct 29 '15 at 08:57