我正在尝试将一系列文件从iPhone上传到服务器,目的是即使应用程序处于后台或暂停,也会上传这些文件。
我正在使用NSURLSession
及其系列API提供的后台传输。
奇怪的是它在2周前完全正常工作。如:
最近几天,我一直在网络模块之外进行一些重构。几天前我再次尝试上传时,只要按下“主页”按钮,上面的步骤(3)。当我再次进入应用程序时,文件上传将停止。上传将恢复。
好像后台上传甚至无法正常工作(对于一个文件,更不用说多个文件了)。
我已多次运行代码,发现它的工作量约为1/50。但其他49次没有。我还检查了以前使用的代码版本(服务器+ iOS),它不再有效 - 或者说,工作很少变化(1/50)
经过rules进行背景转移和Lifecycle of URL Session多次以确保我遵守Apple建议的指导方针,我正在绞尽脑汁想知道什么破坏了,这令人难以置信这是多么不合逻辑 - 我怀疑它不是代码实现。
所以感谢任何帮助...
1)在我的网络类(单身人士)的init
方法中,我初始化NSURLSessionConfiguration
和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)有一种方便的方法叫做实际上传。每个会话只有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
很高兴在这一点上尝试任何事情。谢谢!
当后台上传工作时观察到操作系统调用了application:handleEventsForBackgroundURLSession:completionHandler:
。当它不起作用时,永远不会发出回叫。
我不确定后台上传的先决条件是操作系统必须先杀死应用程序。如果是这样,在什么条件下会发生这种情况?我们可以提示吗?
如上所述,50次中有49次,操作系统会将应用程序保留在后台,并暂停上传。
答案 0 :(得分:2)
有一件事想说清楚,你不能长时间在后台运行任何任务,因为Apple不允许你。只有在特殊情况下Apple才会考虑它。最好在Running background services in iOS
中解释现在回到你的实施问题是它只适用于上传任务的后台,这是在应用程序处于活动状态且任务未完成时启动的。这就是50次尝试中有1次你看到任务在后台工作的原因。
现在要解决您的问题,您必须立即启动所有/一堆上传,以便如果应用程序进入后台,您的应用程序仍然可以上传文件。这个聪明的tutorial解释了与背景转移相关的不同案例。
您也可以试用AFNetworking多部分请求上传。
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];
答案 1 :(得分:1)
我的一个视频应用中也有同样的问题。这个问题发生在2月14日之后。我挖了很多,发现这发生在苹果改变了他们的全球证书之后。请检查此https://developer.apple.com/support/certificates/expiration/
。
解决方案是首先从钥匙串访问中撤消现有证书,然后添加新的开发/分发证书,新的应用程序ID和配置文件。它肯定会奏效。
答案 2 :(得分:1)
我认为问题出在上传部分。您在完成上一个任务后启动下一个上载任务,而不是创建所有上载任务一次。在您的appDelegate中,将application:handleEventsForBackgroundURLSession:completionHandler:
的完成处理程序存储为属性。使用NSURLSessionDelegate
的{{1}} URLSessionDidFinishEventsForBackgroundURLSession:
委托方法调用完成块。在拨打电话之前,请确保完成所有上传任务。本教程http://www.appcoda.com/background-transfer-service-ios7/很好地解释了下载任务。可能是您可以为上传任务应用相同的规则。
答案 3 :(得分:1)
我遇到了类似的问题,文件正在上传。它在后台停了。
在审核文档和类似答案后花了几天时间。This solution worked for me
UIBackgroundTaskIdentifier
是非常有用的重要财产之一。
在app delegate类中创建此内容,并将其初始化为UIBackgroundTaskInvalid
中的didFinishLaunchingWithOptions
。 -
@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;
}
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;
}
}
您可以调试实际设备中的代码,并在适当的网络条件下进行检查。
-
- (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);
}
我希望它对某人有帮助。