目标c – 正确使用beginBackgroundTaskWithExpirationHandler

我有点困惑如何以及何时使用beginBackgroundTaskWithExpirationHandler

苹果在他们的例子中展示了如何在applicationDidEnterBackground委托中使用它,以获得更多的时间来完成一些重要的任务,通常是networking事务。

当看我的应用程序,似乎我的大部分networking的东西是重要的,当一个开始,我想完成它,如果用户按下主页button。

那么, beginBackgroundTaskWithExpirationHandler是否可以安全地包装每一个networking事务(我不是在讨论下载大块数据,大部分是简短的xml)呢?

如果您希望networking交易在后台继续,那么您需要将其包装在后台任务中。 当您完成时调用endBackgroundTask也非常重要,否则在分配的时间到期后应用程序将被终止。

我的倾向是这样的:

 - (void) doUpdate { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self beginBackgroundUpdateTask]; NSURLResponse * response = nil; NSError * error = nil; NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error]; // Do something with the result [self endBackgroundUpdateTask]; }); } - (void) beginBackgroundUpdateTask { self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundUpdateTask]; }]; } - (void) endBackgroundUpdateTask { [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask]; self.backgroundUpdateTask = UIBackgroundTaskInvalid; } 

我有一个UIBackgroundTaskIdentifier属性为每个后台任务


Swift中的等价代码

 func doUpdate () { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { let taskID = beginBackgroundUpdateTask() var response: NSURLResponse?, error: NSError?, request: NSURLRequest? let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error) // Do something with the result endBackgroundUpdateTask(taskID) }) } func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier { return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({}) } func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) { UIApplication.sharedApplication().endBackgroundTask(taskID) } 

被接受的答案是非常有帮助的,在大多数情况下都应该没问题,但有两件事让我困扰:

  1. 如许多人已经注意到的,将任务标识符存储为属性意味着如果多次调用该方法,则可以覆盖该任务标识符,从而导致任务将永远不会被正常结束,直到在期满时被操作系统强制结束。

  2. 这种模式需要一个独特的属性,每个调用beginBackgroundTaskWithExpirationHandler ,如果你有一个更大的应用程序与大量的networking方法,这看起来很麻烦。

为了解决这些问题,我写了一个单例,负责所有的pipe道工作,并跟踪字典中的活动任务。 不需要任何属性来跟踪任务标识符。 似乎运作良好。 用法简化为:

 //start the task NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask]; //do stuff //end the task [[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey]; 

或者,如果你想提供一个完成块来完成除了结束任务之外的任务(内置),你可以调用:

 NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTaskWithCompletionHandler:^{ //do stuff }]; 

下面提供了相关的源代码(为了简洁起见,不包括单身人士的东西)。 意见/反馈欢迎。

 - (id)init { self = [super init]; if (self) { [self setTaskKeyCounter:0]; [self setDictTaskIdentifiers:[NSMutableDictionary dictionary]]; [self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]]; } return self; } - (NSUInteger)beginTask { return [self beginTaskWithCompletionHandler:nil]; } - (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion; { //read the counter and increment it NSUInteger taskKey; @synchronized(self) { taskKey = self.taskKeyCounter; self.taskKeyCounter++; } //tell the OS to start a task that should continue in the background if needed NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endTaskWithKey:taskKey]; }]; //add this task identifier to the active task dictionary [self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //store the completion block (if any) if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //return the dictionary key return taskKey; } - (void)endTaskWithKey:(NSUInteger)_key { @synchronized(self.dictTaskCompletionBlocks) { //see if this task has a completion block CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (completion) { //run the completion block and remove it from the completion block dictionary completion(); [self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; } } @synchronized(self.dictTaskIdentifiers) { //see if this task has been ended yet NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (taskId) { //end the task and remove it from the active task dictionary [[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]]; [self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; } } } 

这是一个封装运行后台任务的Swift类 :

 class BackgroundTask { private let application: UIApplication private var identifier = UIBackgroundTaskInvalid init(application: UIApplication) { self.application = application } class func run(application: UIApplication, handler: (BackgroundTask) -> ()) { // NOTE: The handler must call end() when it is done let backgroundTask = BackgroundTask(application: application) backgroundTask.begin() handler(backgroundTask) } func begin() { self.identifier = application.beginBackgroundTaskWithExpirationHandler { self.end() } } func end() { if (identifier != UIBackgroundTaskInvalid) { application.endBackgroundTask(identifier) } identifier = UIBackgroundTaskInvalid } } 

最简单的方法来使用它:

 BackgroundTask.run(application) { backgroundTask in // Do something backgroundTask.end() } 

如果您在结束之前需要等待委托callback,请使用如下所示:

 class MyClass { backgroundTask: BackgroundTask? func doSomething() { backgroundTask = BackgroundTask(application) backgroundTask!.begin() // Do something that waits for callback } func callback() { backgroundTask?.end() backgroundTask = nil } } 

我实现了Joel的解决scheme。 这里是完整的代码:

.h文件:

 #import <Foundation/Foundation.h> @interface VMKBackgroundTaskManager : NSObject + (id) sharedTasks; - (NSUInteger)beginTask; - (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion; - (void)endTaskWithKey:(NSUInteger)_key; @end 

.m文件:

 #import "VMKBackgroundTaskManager.h" @interface VMKBackgroundTaskManager() @property NSUInteger taskKeyCounter; @property NSMutableDictionary *dictTaskIdentifiers; @property NSMutableDictionary *dictTaskCompletionBlocks; @end @implementation VMKBackgroundTaskManager + (id)sharedTasks { static VMKBackgroundTaskManager *sharedTasks = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedTasks = [[self alloc] init]; }); return sharedTasks; } - (id)init { self = [super init]; if (self) { [self setTaskKeyCounter:0]; [self setDictTaskIdentifiers:[NSMutableDictionary dictionary]]; [self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]]; } return self; } - (NSUInteger)beginTask { return [self beginTaskWithCompletionHandler:nil]; } - (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion; { //read the counter and increment it NSUInteger taskKey; @synchronized(self) { taskKey = self.taskKeyCounter; self.taskKeyCounter++; } //tell the OS to start a task that should continue in the background if needed NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endTaskWithKey:taskKey]; }]; //add this task identifier to the active task dictionary [self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //store the completion block (if any) if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //return the dictionary key return taskKey; } - (void)endTaskWithKey:(NSUInteger)_key { @synchronized(self.dictTaskCompletionBlocks) { //see if this task has a completion block CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (completion) { //run the completion block and remove it from the completion block dictionary completion(); [self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; } } @synchronized(self.dictTaskIdentifiers) { //see if this task has been ended yet NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (taskId) { //end the task and remove it from the active task dictionary [[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]]; [self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; NSLog(@"Task ended"); } } } @end 
  • (void)doUpdate {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {

     [self beginBackgroundUpdateTask]; NSURLResponse * response = nil; NSError * error = nil; NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: 

    &错误];

     // Do something with the result [self endBackgroundUpdateTask]; 

    }); }

  • (void)beginBackgroundUpdateTask {self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^ {[self endBackgroundUpdateTask]; }]; }

  • (void)endBackgroundUpdateTask {[[UIApplication sharedApplication] endBackgroundTask:self.backgroundUpdateTask]; self.backgroundUpdateTask = UIBackgroundTaskInvalid; }

谢谢阿什利米尔斯,这对我来说是完美的