我有点困惑如何以及何时使用beginBackgroundTaskWithExpirationHandler
。
苹果公司表示在他们的例子来使用它applicationDidEnterBackground
代表,以获得更多的时间来完成一些重要的任务,通常是一个网络交易。
当我的应用程序来看,它似乎像极了我的网络的东西是重要的,当一个人开始,我想,当用户按下home键来完成它。
因此,它是公认的/好的做法来包装每一个网络交易(我不是在谈论下载数据,它主要是一些短期XML的大块)与beginBackgroundTaskWithExpirationHandler
是在安全方面?
Answer 1:
如果你希望你的网络交易继续在后台运行,那么你就需要将其包装在一个后台任务。 这也是非常重要的,你叫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
每个后台任务属性
在斯威夫特等效代码
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)
}
Answer 2:
接受的答案是非常有益的,应该是在大多数情况下正常,但是两件事情困扰着我吧:
正如一些人所指出的,存储任务标识符作为一个属性意味着它可以,如果该方法被多次调用被覆盖,导致这将永远是优雅地结束,直到被迫操作系统的时候到期结束任务。
这种模式要求每次调用一个独特的属性beginBackgroundTaskWithExpirationHandler
,如果你有很多的网络方法更大的应用程序,它看起来笨重。
为了解决这些问题,我写了一个单身是通吃管道的关怀和字典中的跟踪活动的任务。 没有性需要跟踪任务的标识符。 似乎运作良好。 使用简化为:
//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]];
}
}
}
Answer 3:
这里是一个斯威夫特类封装运行的后台任务:
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()
}
如果您需要等待一个委托回调你结束前,再使用这样的事情:
class MyClass {
backgroundTask: BackgroundTask?
func doSomething() {
backgroundTask = BackgroundTask(application)
backgroundTask!.begin()
// Do something that waits for callback
}
func callback() {
backgroundTask?.end()
backgroundTask = nil
}
}
Answer 4:
我实现了乔尔的解决方案。 下面是完整的代码:
.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
Answer 5:
首先请阅读文档: https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio
后台任务应满足下列要求:
- 后台任务应该尽快报道,但它并没有成为我们真正的任务开始之前。 方法
beginBackgroundTaskWithExpirationHandler:
异步工作,所以如果它被称为在年底applicationDidEnterBackground:
那么它将无法注册后台任务,并会立即调用到期处理。 - 到期处理程序必须取消我们的真正任务和标记后台任务的结束。 它迫使我们不断保存起来的后台任务标识,例如一些类的属性。 这个属性应该是我们控制的,它不能被覆盖之下。
- 到期处理从主线程中执行,所以如果你想取消它在那里,你真正的任务应该是线程安全的。
- 我们真正的任务应该是取消。 这意味着我们真正的任务应该有方法
cancel
。 否则,就是它会在即使我们纪念后台任务,结束难以预料的方式被终止的风险。 - 包含代码
beginBackgroundTaskWithExpirationHandler:
可称为无处不在任何线程。 它不必是应用程序委托的方法applicationDidEnterBackground:
- 有没有感觉这样做对在法的情况下同步操作超过5秒短
applicationDidEnterBackground:
请阅读文档https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground?language=objc ) - 方法
applicationDidEnterBackground
必须在时间超过5秒,因此所有后台任务应该在第二个线程推出更短的执行。
例:
class MySpecificBackgroundTask: NSObject, URLSessionDataDelegate {
// MARK: - Properties
let application: UIApplication
var backgroundTaskIdentifier: UIBackgroundTaskIdentifier
var task: URLSessionDataTask? = nil
// MARK: - Initializers
init(application: UIApplication) {
self.application = application
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid
}
// MARK: - Actions
func start() {
self.backgroundTaskIdentifier = self.application.beginBackgroundTask {
self.cancel()
}
self.startUrlRequest()
}
func cancel() {
self.task?.cancel()
self.end()
}
private func end() {
self.application.endBackgroundTask(self.backgroundTaskIdentifier)
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid
}
// MARK: - URLSession methods
private func startUrlRequest() {
let sessionConfig = URLSessionConfiguration.background(withIdentifier: "MySpecificBackgroundTaskId")
let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
guard let url = URL(string: "https://example.com/api/my/path") else {
self.end()
return
}
let request = URLRequest(url: url)
self.task = session.dataTask(with: request)
self.task?.resume()
}
// MARK: - URLSessionDataDelegate methods
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
self.end()
}
// Implement other methods of URLSessionDataDelegate to handle response...
}
它可以在我们的应用程序委托中使用:
func applicationDidEnterBackground(_ application: UIApplication) {
let myBackgroundTask = MySpecificBackgroundTask(application: application)
myBackgroundTask.start()
}
文章来源: Proper use of beginBackgroundTaskWithExpirationHandler