xcode URLSessionDidFinishEventsForBackgroundURLSession 未调用-Objective-C

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32676352/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 07:52:52  来源:igfitidea点击:

URLSessionDidFinishEventsForBackgroundURLSession Not Calling- Objective-C

iosobjective-cxcodensurlsessionnsurlsessiondownloadtask

提问by iReddy

NSURLSessionDelegate method
URLSessionDidFinishEventsForBackgroundURLSessionis not Calling ?

NSURLSession委托方法
URLSessionDidFinishEventsForBackgroundURLSession是不是在调用?

I already enabled the Background Modesin project capabilities settings.

我已经在项目功能设置中启用了后台模式

Here is the code

这是代码

AppDelegate.hMethod

AppDelegate.h方法

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (nonatomic, copy) void(^backgroundTransferCompletionHandler)();

@end

AppDelegate.mMethod

AppDelegate.m方法

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{

    self.backgroundTransferCompletionHandler = completionHandler;

}

ViewController.mMethod

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 Method

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];
                }];
            }
        }
    }];
}

I'm able to download all the files one by one but After downloading all the files, URLSessionDidFinishEventsForBackgroundURLSessionmethod is not calling .

我可以一一下载所有文件,但下载所有文件后,URLSessionDidFinishEventsForBackgroundURLSession方法未调用 .

I have to perform some action method After Downloading all the files only.

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

回答by Rob

These delegate methods won't get called if:

如果出现以下情况,将不会调用这些委托方法:

  1. The app is already running when the tasks finish;

  2. The app was terminated by double-tapping on the device's home button and manually killing it; or

  3. If you fail to start a background NSURLSessionwith the same identifier.

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

  2. 通过双击设备的主页按钮并手动杀死它来终止应用程序;或者

  3. 如果您无法NSURLSession使用相同的标识符启动背景。

So, the obvious questions are:

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

  • How was the app terminated? If not terminated, or if terminated incorrectly (e.g. you manually kill it by double-tapping on the home button), that will prevent these delegate methods from getting called.

  • Are you seeing handleEventsForBackgroundURLSessioncalled at all?

  • Are you doing this on a physical device? This behaves differently on the simulator.

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

  • 你看到handleEventsForBackgroundURLSession叫了吗?

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

Bottom line, there's not enough here to diagnose the precise problem, but these are common reasons why that delegate method might not get called.

最重要的是,这里还不足以诊断出确切的问题,但这些是可能无法调用该委托方法的常见原因。

You later said:

你后来说:

Actually this is the first time I'm using NSURLSessionclass. 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类。我的实际要求是一旦下载(所有图像)完成,那么我只能从文档目录中检索图像,并且可以在UICollectionView.

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

If that's your requirement, then background NSURLSessionmight be overkill. It's slower than standard NSURLSession, and more complicated. Only use background sessions if you really need large downloads to continue in the background after the app is suspended/terminated.

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

That tutorial you reference seems like a passable introduction to a pretty complicated topic (though I disagree with the URLSessionDidFinish...implementation, as discussed in comments). I would do something like:

您参考的那个教程似乎是对一个相当复杂的主题的合格介绍(尽管我不同意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;
        }
    });
}

The question in my mind is whether you really want background session at all if you're just downloading images for collection view. I'd only do that if there were so many images (or they were so large) that they couldn't be reasonably downloaded while the app was still running.

我心中的问题是,如果您只是为收藏视图下载图像,您是否真的想要后台会话。如果图像太多(或者它们太大)以至于在应用程序仍在运行时无法合理下载它们,我只会这样做。



For the sake of completeness, I'll share a full demonstration of background downloads below:

为了完整起见,我将在下面分享后台下载的完整演示:

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

And

//  SessionManager.h

@import UIKit;

@interface SessionManager : NSObject

@property (nonatomic, copy) void (^savedCompletionHandler)();

+ (instancetype)sharedSession;
- (void)startDownload:(NSURL *)url;

@end

and

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

And, finally, the view controller code that initiates the request:

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

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

回答by Alok SInha

As stated by Apple:

正如苹果所说:

If an iOS app is terminated by the system and relaunched, the app can use the same identifier to create a new configuration object and session and retrieve the status of transfers that were in progress at the time of termination. This behavior applies only for normal termination of the app by the system. If the user terminates the app from the multitasking screen, the system cancels all of the session's background transfers. In addition, the system does not automatically relaunch apps that were force quit by the user. The user must explicitly relaunch the app before transfers can begin again.

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