NSURLSession后台上传无法正常工作

我正在尝试将一系列文件从iPhone上传到服务器,目的是即使应用程序处于后台或暂停,也会上传这些文件。

我正在使用NSURLSession及其系列API提供的后台传输

奇怪的是它在两周前完全正常工作 。 如:

  1. 我会点击UI上的“上传”按钮
  2. 文件将开始在我的服务器上逐一显示
  3. 我点击iPhone上的“主页”按钮,让应用程序进入后台
  4. 文件将继续上传到服务器,直到它们全部完成

最近几天,我一直在网络模块之外进行一些重构。 几天前我再次尝试上传时,只要按下“主页”按钮,上面的步骤(3)。 当我再次进入应用程序时,文件上传将停止。 上传将恢复。

这就好像后台上传甚至不起作用(对于一个文件,更不用说多个文件)。

我已多次运行代码,发现它的工作量约为1/50。 但其他49次没有。 我还检查了以前使用的代码版本(服务器+ iOS),它不再有效 – 或者说,工作很少变化(1/50)

多次完成背景传输和URL会话生命周期的规则 ,以确保我遵守Apple提出的指导方针,我正在绞尽脑汁想知道是什么破坏了,这让人难以理解这是多么不合逻辑 – 我怀疑它是代码实现以外的东西。

所以任何帮助都表示赞赏……

履行

1)在我的网络类(单例)的init方法中,我初始化了NSURLSessionConfigurationNSURLSession

  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)有一种方便的方法叫做实际上传。 每个会话只有1个上传任务:

  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)在didCompleteWithError委托中,我检查是否所有文件都已上传,如果没有,则移动到下一个文件 – GLOBAL.uploadQueue是我保留对我必须上传的所有文件的引用的地方, 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将准备文件并调用上传的便捷方法(上面的(2))。

以下是穴居人调试的一些示例输出(对于文件2 – 4)。 请注意,一旦应用进入后台,上传就会停止。

注意,我也等了比下面输出中显示的10秒更长的时间。 最长的是我离开吃饭(30分钟),回来后上传结束了。 一旦应用程序在后台,操作系统从未选择它。

 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 

很高兴在这一点上尝试任何事情。 谢谢!

编辑#1

在后台上传工作时观察application:handleEventsForBackgroundURLSession:completionHandler:由OS调用。 当它不起作用时,永远不会发出回叫。

我不确定后台上传的先决条件是操作系统必须先杀死应用程序。 如果是这样,在什么条件下会发生这种情况? 我们可以提示吗?

如前所述,50次中有49次,操作系统会将应用程序保留在后台,并暂停上传。

有一件事想说清楚,你不能长时间在后台运行任何任务,因为Apple不允许你。 只有在特殊情况下Apple才会考虑它。 最好的解释在iOS中运行后台服务

现在回到您的实施问题是它只适用于上传任务的后台,当应用程序处于活动状态并且任务仍未完成时启动。 这就是50次尝试中有1次你看到任务在后台工作的原因。

现在要解决您的问题,您必须立即启动所有/一堆上传,以便如果应用程序进入后台仍然您的应用程序将能够上传文件。 这个愚蠢的教程解释了与Background Transfer相关的不同案例。

您也可以尝试AFNetworking多部分上传请求。

 NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id 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]; 

我的一个video应用程序中也有同样的问题。 这个问题发生在2月14日之后。我挖了很多,发现这发生在苹果改变了他们的全球证书之后。 请查看此https://developer.apple.com/support/certificates/expiration/ 。 解决方案是首先从密钥链访问中撤消现有证书,然后添加新的开发/分发证书,新的应用程序ID和配置文件。 它肯定会奏效。

我认为问题出在上传部分。 您在完成上一个任务后启动下一个上载任务,而不是创建所有上载任务一次。 在appDelegate中, application:handleEventsForBackgroundURLSession:completionHandler:的完成处理application:handleEventsForBackgroundURLSession:completionHandler:作为属性。 在NSURLSessionDelegate调用完成块URLSessionDidFinishEventsForBackgroundURLSession:委托方法。 在拨打电话之前,请确保完成所有上传任务。 本教程http://www.appcoda.com/background-transfer-service-ios7/很好地解释了下载任务。 可能是您可以为上传任务应用相同的规则。

我遇到了类似的问题,文件在前台上传。 它在后台停了下来。

在花了几天时间审阅文档和类似的答案之后。 这个解决方案对我有用

  1. UIBackgroundTaskIdentifier是非常有用的重要属性之一。 在app delegate类中创建它并将其初始化为didFinishLaunchingWithOptions UIBackgroundTaskInvalid

 @interface AppDelegate () @property (atomic) UIBackgroundTaskIdentifier bgTask; @property (nonatomic, weak) NSTimer *timer; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.bgTask = UIBackgroundTaskInvalid; return YES; } 
  1. 应用程序正在将状态从Foreground转换为Background,反之亦然。 我们需要确保在其相关的委托方法中检查此属性。

beginBackgroundTaskWithExpirationHandler对于实际在后台启动后台任务非常重要。

 - (void)applicationDidEnterBackground:(UIApplication *)application { self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ if (self.bgTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; } }]; self.timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(startTracking) userInfo:nil repeats:YES]; } - (void)applicationWillEnterForeground:(UIApplication *)application { // invalidate the timer if still running [self.timer invalidate]; // end the background task if still present if (self.bgTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; } } 

您可以在真实设备中调试代码并在适当的网络条件下进行检查。

  1. 此外,您可以添加以下委托方法来跟踪bytesSent。

 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { // Periodically informs the delegate of the progress of sending body content to the server. // Compute progress percentage float progress = (float)totalBytesSent / (float)totalBytesExpectedToSend; // Compute time executed so far NSDate *stopTime = [NSDate date]; NSTimeInterval executionTime = [stopTime timeIntervalSinceDate:startTime]; // Send info to console NSLog(@"%s bytesSent = %lld, totalBytesSent: %lld, totalBytesExpectedToSend: %lld, progress %.3f, time (s): %.1f", __PRETTY_FUNCTION__, bytesSent, totalBytesSent, totalBytesExpectedToSend, progress*100, executionTime); } 

我希望它对某人有帮助。