109

をいつ、どのように使用するかについて少し混乱していますbeginBackgroundTaskWithExpirationHandler

Apple は例の中でapplicationDidEnterBackgroundデリゲートでそれを使用して、重要なタスク (通常はネットワーク トランザクション) を完了するためにより多くの時間を確保することを示しています。

私のアプリを見ると、私のネットワーク関連のほとんどが重要であるように見えます.1つが開始されたら、ユーザーがホームボタンを押したらそれを完了したいと思います.

安全のために、すべてのネットワークトランザクションをラップすることは受け入れられています beginBackgroundTaskWithExpirationHandlerか?

4

5 に答える 5

167

ネットワーク トランザクションをバックグラウンドで続行する場合は、バックグラウンド タスクでラップする必要があります。終了時に呼び出すことも非常に重要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: 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)
}
于 2012-04-25T16:23:42.123 に答える
9

ここおよびその他の SO の質問への回答に記載されているようbeginBackgroundTaskに、アプリがバックグラウンドに移行する場合にのみ使用する必要はありません。逆に、アプリがバックグラウンドに移行したとしても、確実に完了さたい時間のかかる操作には、バックグラウンド タスクを使用する必要があります。

beginBackgroundTaskしたがって、あなたのコードは、呼び出しとendBackgroundTask一貫性のために同じ定型コードの繰り返しで終わる可能性があります。この繰り返しを防ぐために、ボイラープレートを単一のカプセル化されたエンティティにパッケージ化することは確かに合理的です。

それを行うための既存の回答のいくつかが気に入っていますが、最良の方法は Operation サブクラスを使用することだと思います:

  • Operation を任意の OperationQueue にエンキューし、必要に応じてそのキューを操作できます。たとえば、キュ​​ーにある既存の操作を途中で自由にキャンセルできます。

  • やるべきことが複数ある場合は、複数のバックグラウンド タスク操作を連鎖させることができます。操作は依存関係をサポートします。

  • オペレーション キューは、バックグラウンド キューにすることができます (またそうする必要があります)。したがって、Operationは非同期コードであるため、タスク内で非同期コードを実行することについて心配する必要はありません。(実際、オペレーション内で別のレベルの非同期コードを実行しても意味がありません。そのコードが開始される前にオペレーションが終了するためです。それを行う必要がある場合は、別のオペレーションを使用します。)

考えられる Operation サブクラスは次のとおりです。

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

これを使用する方法は明らかですが、そうでない場合は、グローバルな OperationQueue があると想像してください。

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

したがって、典型的な時間のかかるコードのバッチについては、次のようになります。

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

時間のかかるコードのバッチを段階に分割できる場合は、タスクがキャンセルされた場合に早期に辞退することをお勧めします。その場合は、閉鎖から時期尚早に戻ってください。クロージャー内からのタスクへの参照は弱い必要があることに注意してください。そうしないと、保持サイクルが発生します。これは人工的な図です。

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)

バックグラウンド タスク自体が途中でキャンセルされた場合にクリーンアップを行う必要がある場合に備えて、オプションのcleanupハンドラー プロパティを用意しました (前の例では使用されていません)。他のいくつかの回答は、それを含めなかったことで批判されました。

于 2019-05-02T18:37:40.203 に答える