URLSessionDidFinishEventsForBackgroundURLSession 未调用-Objective-C

Posted

技术标签:

【中文标题】URLSessionDidFinishEventsForBackgroundURLSession 未调用-Objective-C【英文标题】:URLSessionDidFinishEventsForBackgroundURLSession Not Calling- Objective-C 【发布时间】:2015-09-20 06:07:11 【问题描述】:

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方法没有调用。

仅在下载所有文件后,我必须执行一些操作方法。

【问题讨论】:

顺便说一句,我觉得getTasksWithCompletionHandler 的使用很奇怪,因为URLSessionDidFinishEvents... 只有在任务完成时才会被调用。这对我来说似乎是多余的(尽管可能与您手头的问题无关)。如果这种情况发生,调用handleEventsForBackgroundURLSession,然后调用URLSessionDidFinish...,你就有了一条不调用completionHandler的路径。我认为这条路永远不会实现,但如果实现了,你就会引入一个新问题。 其实这是我第一次使用 NSUrlSession 类。我的实际要求是一旦下载(所有图像)完成,那么只有我可以从文档目录中检索图像并且我可以在 UICollectionVeiw 中显示 我正在关注 APPCODA 的本教程。这是链接appcoda.com/background-transfer-service-ios7 我看了那个教程,我认为它总体上很好,尽管我不同意URLSessionDidFinishEventsForBackgroundURLSession 的实现,正如我上面提到的。但我认为这不是你的问题。 【参考方案1】:

在以下情况下不会调用这些委托方法:

    任务完成时应用程序已经在运行;

    通过双击设备的主页按钮并手动将其终止,应用程序被终止;或

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

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

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

你看到handleEventsForBackgroundURLSession 被调用了吗?

您是在物理设备上执行此操作吗?这在模拟器上表现不同。

归根结底,这里不足以诊断确切的问题,但这些是可能无法调用委托方法的常见原因。

你后来说:

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

我正在学习 APPCODA 的本教程。这是链接http://appcoda.com/background-transfer-service-ios7

如果这是您的要求,那么后台 NSURLSession 可能会过大。它比标准的NSURLSession 慢,而且更复杂。如果您确实需要在应用暂停/终止后在后台继续进行大量下载,请仅使用后台会话。

您参考的那个教程似乎是对一个相当复杂的主题的通俗介绍(尽管我不同意 URLSessionDidFinish... 实现,如 cmets 中所讨论的那样)。我会这样做:

- (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://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

【讨论】:

其实这是我第一次使用 NSUrlSession 类。我的实际要求是一旦下载(所有图像)完成,那么只有我可以从文档目录中检索图像并且我可以在 UICollectionVeiw 中显示。我正在关注 APPCODA 的本教程。这是链接 appcoda.com/background-transfer-service-ios7 – @Rob 该方法永远不会在模拟器(9.2)中被调用。但是我还没有在设备上测试过。 @RajanMaheshwari 是的,我建议在设备上进行测试。在设备上,它确实会被调用。 这非常有帮助!【参考方案2】:

正如苹果所说:

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

【讨论】:

以上是关于URLSessionDidFinishEventsForBackgroundURLSession 未调用-Objective-C的主要内容,如果未能解决你的问题,请参考以下文章