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
URLSessionDidFinishEventsForBackgroundURLSession Not Calling- Objective-C
提问by iReddy
NSURLSession
Delegate method URLSessionDidFinishEventsForBackgroundURLSession
is 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, URLSessionDidFinishEventsForBackgroundURLSession
method 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:
如果出现以下情况,将不会调用这些委托方法:
The app is already running when the tasks finish;
The app was terminated by double-tapping on the device's home button and manually killing it; or
If you fail to start a background
NSURLSession
with the same identifier.
任务完成时应用程序已经在运行;
通过双击设备的主页按钮并手动杀死它来终止应用程序;或者
如果您无法
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
handleEventsForBackgroundURLSession
called 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
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
类。我的实际要求是一旦下载(所有图像)完成,那么我只能从文档目录中检索图像,并且可以在UICollectionView
.我正在关注 APPCODA 的本教程。这是链接http://appcoda.com/background-transfer-service-ios7
If that's your requirement, then background NSURLSession
might 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 应用程序被系统终止并重新启动,该应用程序可以使用相同的标识符来创建新的配置对象和会话,并检索终止时正在进行的传输状态。此行为仅适用于系统正常终止应用程序。如果用户从多任务屏幕终止应用程序,系统将取消会话的所有后台传输。此外,系统不会自动重新启动用户强制退出的应用程序。用户必须明确重新启动应用程序才能再次开始传输。