正确使用beginBackgroundTaskWithExpirationHandler的正确使用b

2019-05-13 16:12发布

我有点困惑如何以及何时使用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:

接受的答案是非常有益的,应该是在大多数情况下正常,但是两件事情困扰着我吧:

  1. 正如一些人所指出的,存储任务标识符作为一个属性意味着它可以,如果该方法被多次调用被覆盖,导致这将永远是优雅地结束,直到被迫操作系统的时候到期结束任务。

  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