URLSessionDidFinishEventsForBackgroundURLSession 未调用 - Objective-C
URLSessionDidFinishEventsForBackgroundURLSession Not Calling- Objective-C
NSURLSession
委托方法
URLSessionDidFinishEventsForBackgroundURLSession
不是在通话吗?
我已经在项目功能设置中启用了后台模式。
这是代码
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
方法没有调用。
我必须在仅下载所有文件后执行一些操作方法。
如果出现以下情况,将不会调用这些委托方法:
当任务完成时,应用程序已经 运行;
该应用程序已通过 double-tapping 在设备的主页按钮上终止并手动将其终止;或
如果启动后台失败NSURLSession
同一个标识符
所以,显而易见的问题是:
应用程序是如何终止的?如果没有终止,或者如果终止不正确(例如,您通过主页按钮上的 double-tapping 手动终止它),这将阻止调用这些委托方法。
你看到 handleEventsForBackgroundURLSession
被调用了吗?
您是在物理设备上执行此操作吗?这在模拟器上的表现不同。
最重要的是,此处不足以诊断出确切的问题,但这些是可能无法调用该委托方法的常见原因。
你后来说:
Actually this is the first time I'm using NSURLSession
class. My actual requirement is once the download (all the images) is completed then only I can retrieve the images from document directory and I can show in UICollectionView
.
I'm following this tutorial from APPCODA. Here is the link http://appcoda.com/background-transfer-service-ios7
如果那是您的要求,那么背景 NSURLSession
可能有点矫枉过正。它比标准 NSURLSession
慢,而且更复杂。如果您确实需要大量下载以便在应用 suspended/terminated.
后在后台继续,请仅使用后台 sessions
您引用的那个教程似乎是对一个相当复杂的主题的通俗介绍(尽管我不同意 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;
}
});
}
我的问题是,如果您只是为 collection 视图下载图像,您是否真的需要背景 session。只有当图像太多(或者它们太大)以至于在应用程序仍处于 运行.
时无法合理下载它们时,我才会这样做
为了完整起见,我将在下面分享后台下载的完整演示:
// 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://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/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
如 Apple 所述:
如果 iOS 应用程序被系统终止并重新启动,该应用程序可以使用相同的标识符来创建新的配置对象和会话,并检索在终止。此行为仅适用于系统正常终止应用程序。如果用户从多任务屏幕终止应用程序,系统将取消所有会话的后台传输。此外,系统不会自动重新启动用户强制退出的应用程序。用户必须明确重新启动应用程序才能再次开始传输。
NSURLSession
委托方法
URLSessionDidFinishEventsForBackgroundURLSession
不是在通话吗?
我已经在项目功能设置中启用了后台模式。
这是代码
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
方法没有调用。
我必须在仅下载所有文件后执行一些操作方法。
如果出现以下情况,将不会调用这些委托方法:
当任务完成时,应用程序已经 运行;
该应用程序已通过 double-tapping 在设备的主页按钮上终止并手动将其终止;或
如果启动后台失败
NSURLSession
同一个标识符
所以,显而易见的问题是:
应用程序是如何终止的?如果没有终止,或者如果终止不正确(例如,您通过主页按钮上的 double-tapping 手动终止它),这将阻止调用这些委托方法。
你看到
handleEventsForBackgroundURLSession
被调用了吗?您是在物理设备上执行此操作吗?这在模拟器上的表现不同。
最重要的是,此处不足以诊断出确切的问题,但这些是可能无法调用该委托方法的常见原因。
你后来说:
Actually this is the first time I'm using
NSURLSession
class. My actual requirement is once the download (all the images) is completed then only I can retrieve the images from document directory and I can show inUICollectionView
.I'm following this tutorial from APPCODA. Here is the link http://appcoda.com/background-transfer-service-ios7
如果那是您的要求,那么背景 NSURLSession
可能有点矫枉过正。它比标准 NSURLSession
慢,而且更复杂。如果您确实需要大量下载以便在应用 suspended/terminated.
您引用的那个教程似乎是对一个相当复杂的主题的通俗介绍(尽管我不同意 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;
}
});
}
我的问题是,如果您只是为 collection 视图下载图像,您是否真的需要背景 session。只有当图像太多(或者它们太大)以至于在应用程序仍处于 运行.
时无法合理下载它们时,我才会这样做为了完整起见,我将在下面分享后台下载的完整演示:
// 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://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
@"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/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
如 Apple 所述:
如果 iOS 应用程序被系统终止并重新启动,该应用程序可以使用相同的标识符来创建新的配置对象和会话,并检索在终止。此行为仅适用于系统正常终止应用程序。如果用户从多任务屏幕终止应用程序,系统将取消所有会话的后台传输。此外,系统不会自动重新启动用户强制退出的应用程序。用户必须明确重新启动应用程序才能再次开始传输。