目标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) }
被接受的答案是非常有帮助的,在大多数情况下都应该没问题,但有两件事让我困扰:
-
如许多人已经注意到的,将任务标识符存储为属性意味着如果多次调用该方法,则可以覆盖该任务标识符,从而导致任务将永远不会被正常结束,直到在期满时被操作系统强制结束。
-
这种模式需要一个独特的属性,每个调用
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; }
谢谢阿什利米尔斯,这对我来说是完美的