ios 正确使用 beginBackgroundTaskWithExpirationHandler

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10319643/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:04:35  来源:igfitidea点击:

Proper use of beginBackgroundTaskWithExpirationHandler

iosobjective-cnetwork-programmingmultitaskingbackground-thread

提问by Eyal

I'm a bit confused about how and when to use beginBackgroundTaskWithExpirationHandler.

我对如何以及何时使用beginBackgroundTaskWithExpirationHandler.

Apple shows in their examples to use it in applicationDidEnterBackgrounddelegate, to get more time to complete some important task, usually a network transaction.

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

When looking on my app, it seems like most of my network stuff is important, and when one is started I would like to complete it if the user pressed the home button.

在查看我的应用程序时,似乎我的大部分网络内容都很重要,当一个应用程序启动时,如果用户按下主页按钮,我想完成它。

So is it accepted/good practice to wrap every network transaction (and I'm not talking about downloading big chunk of data, it mostly some short xml) with beginBackgroundTaskWithExpirationHandlerto be on the safe side?

那么,beginBackgroundTaskWithExpirationHandler为了安全起见,包装每个网络事务(我不是在谈论下载大块数据,主要是一些简短的 xml)是否可以接受/好的做法 ?

回答by Ashley Mills

If you want your network transaction to continue in the background, then you'll need to wrap it in a background task. It's also very important that you call endBackgroundTaskwhen you're finished - otherwise the app will be killed after its allotted time has expired.

如果您希望网络事务在后台继续进行,则需要将其包装在后台任务中。endBackgroundTask完成后调用也非常重要- 否则应用程序将在分配的时间到期后被终止。

Mine tend look something like this:

我的往往看起来像这样:

- (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;
}

I have a UIBackgroundTaskIdentifierproperty for each background task

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



Equivalent code in Swift

Swift 中的等效代码

func doUpdate () {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

        let taskID = beginBackgroundUpdateTask()

        var response: URLResponse?, 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.shared.beginBackgroundTask(expirationHandler: ({}))
}

func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
    UIApplication.shared.endBackgroundTask(taskID)
}

回答by Joel

The accepted answer is very helpful and should be fine in most cases, however two things bothered me about it:

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

  1. As a number of people have noted, storing the task identifier as a property means that it can be overwritten if the method is called multiple times, leading to a task that will never be gracefully ended until forced to end by the OS at the time expiration.

  2. This pattern requires a unique property for every call to beginBackgroundTaskWithExpirationHandlerwhich seems cumbersome if you have a larger app with lots of network methods.

  1. 正如许多人所指出的,将任务标识符存储为属性意味着如果多次调用该方法,它可以被覆盖,导致任务永远不会优雅地结束,直到在时间到期时被操作系统强制结束.

  2. 这种模式要求每次调用都有一个独特的属性,beginBackgroundTaskWithExpirationHandler如果您有一个包含大量网络方法的较大应用程序,这似乎很麻烦。

To solve these issues, I wrote a singleton that takes care of all the plumbing and tracks active tasks in a dictionary. No properties needed to keep track of task identifiers. Seems to work well. Usage is simplified to:

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

//start the task
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask];

//do stuff

//end the task
[[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey];

Optionally, if you want to provide a completion block that does something beyond ending the task (which is built in) you can call:

或者,如果您想提供一个完成块来完成任务(内置)之外的其他事情,您可以调用:

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

Relevant source code available below (singleton stuff excluded for brevity). Comments/feedback welcome.

下面提供了相关的源代码(为简洁起见,排除了单一的东西)。欢迎评论/反馈。

- (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]];

        }

    }
}

回答by phatmann

Here is a Swift classthat encapsulates running a background task:

这是一个封装运行后台任务的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
    }
}

The simplest way to use it:

最简单的方式来使用它:

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

If you need to wait for a delegate callback before you end, then use something like this:

如果您需要在结束之前等待委托回调,请使用以下内容:

class MyClass {
    backgroundTask: BackgroundTask?

    func doSomething() {
        backgroundTask = BackgroundTask(application)
        backgroundTask!.begin()
        // Do something that waits for callback
    }

    func callback() {
        backgroundTask?.end()
        backgroundTask = nil
    } 
}

回答by matt

As noted here and in answers to other SO questions, you do NOT want to use beginBackgroundTaskonly just when your app will go into the background; on the contrary, you should use a background task for anytime-consuming operation whose completion you want to ensure even if the app doesgo into the background.

正如此处以及其他 SO 问题的答案中所指出的,您不想beginBackgroundTask仅在您的应用程序进入后台时才使用;相反,您应该为任何耗时的操作使用后台任务,即使应用程序确实进入后台,您也希望确保其完成。

Therefore your code is likely to end up peppered with repetitions of the same boilerplate code for calling beginBackgroundTaskand endBackgroundTaskcoherently. To prevent this repetition, it is certainly reasonable to want to package up the boilerplate into some single encapsulated entity.

因此,您的代码最终可能会重复相同的样板代码以进行调用beginBackgroundTaskendBackgroundTask连贯。为了防止这种重复,想要将样板打包成某个单独的封装实体当然是合理的。

I like some of the existing answers for doing that, but I think the best way is to use an Operation subclass:

我喜欢这样做的一些现有答案,但我认为最好的方法是使用 Operation 子类:

  • You can enqueue the Operation onto any OperationQueue and manipulate that queue as you see fit. For example, you are free to cancel prematurely any existing operations on the queue.

  • If you have more than one thing to do, you can chain multiple background task Operations. Operations support dependencies.

  • The Operation Queue can (and should) be a background queue; thus, there is no need to worry about performing asynchronous code inside your task, because the Operation isthe asynchronous code. (Indeed, it makes no sense to execute anotherlevel of asynchronous code inside an Operation, as the Operation would finish before that code could even start. If you needed to do that, you'd use another Operation.)

  • 您可以将 Operation 排入任何 OperationQueue 并按照您认为合适的方式操作该队列。例如,您可以提前取消队列上的任何现有操作。

  • 如果你有不止一件事情要做,你可以链接多个后台任务操作。操作支持依赖。

  • 操作队列可以(也应该)是后台队列;因此,无需担心在您的任务中执行异步代码,因为 Operation异步代码。(实际上,在 Operation 中执行另一层异步代码是没有意义的,因为 Operation 会在该代码开始之前完成。如果您需要这样做,您将使用另一个 Operation。)

Here's a possible Operation subclass:

这是一个可能的操作子类:

class BackgroundTaskOperation: Operation {
    var whatToDo : (() -> ())?
    var cleanup : (() -> ())?
    override func main() {
        guard !self.isCancelled else { return }
        guard let whatToDo = self.whatToDo else { return }
        var bti : UIBackgroundTaskIdentifier = .invalid
        bti = UIApplication.shared.beginBackgroundTask {
            self.cleanup?()
            self.cancel()
            UIApplication.shared.endBackgroundTask(bti) // cancellation
        }
        guard bti != .invalid else { return }
        whatToDo()
        guard !self.isCancelled else { return }
        UIApplication.shared.endBackgroundTask(bti) // completion
    }
}

It should be obvious how to use this, but in case it isn't, imagine we have a global OperationQueue:

如何使用它应该是显而易见的,但如果不是,请想象我们有一个全局 OperationQueue:

let backgroundTaskQueue : OperationQueue = {
    let q = OperationQueue()
    q.maxConcurrentOperationCount = 1
    return q
}()

So for a typical time-consuming batch of code we would say:

因此,对于典型的耗时的一批代码,我们会说:

let task = BackgroundTaskOperation()
task.whatToDo = {
    // do something here
}
backgroundTaskQueue.addOperation(task)

If your time-consuming batch of code can be divided into stages, you might want to bow out early if your task is cancelled. In that case, just return prematurely from the closure. Note that your reference to the task from within the closure needs to be weak or you'll get a retain cycle. Here's an artificial illustration:

如果您的一批耗时的代码可以分为多个阶段,那么如果您的任务被取消,您可能希望尽早退出。在这种情况下,只需从关闭中过早返回即可。请注意,您在闭包中对任务的引用需要弱,否则您将获得一个保留周期。这是一个人工插图:

let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
    guard let task = task else {return}
    for i in 1...10000 {
        guard !task.isCancelled else {return}
        for j in 1...150000 {
            let k = i*j
        }
    }
}
backgroundTaskQueue.addOperation(task)

In case you have cleanup to do in case the background task itself is cancelled prematurely, I've provided an optional cleanuphandler property (not used in the preceding examples). Some other answers were criticised for not including that.

如果您有清理工作要做,以防后台任务本身过早取消,我提供了一个可选的cleanup处理程序属性(未在前面的示例中使用)。其他一些答案因不包括这一点而受到批评。

回答by vomako

I implemented Joel's solution. Here is the complete code:

我实施了乔尔的解决方案。这是完整的代码:

.h file:

.h 文件:

#import <Foundation/Foundation.h>

@interface VMKBackgroundTaskManager : NSObject

+ (id) sharedTasks;

- (NSUInteger)beginTask;
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
- (void)endTaskWithKey:(NSUInteger)_key;

@end

.m file:

.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