Background Task Completion

# この記事は

iOSで利用可能なバックグラウンド処理の1つである「BackgroundTaskCompletion」 についてのまとめと挙動の検証

# 機能概要

- 特徴
    - フォアグラウンドでやり残した作業をバックグラウンド状態でも引き継いで実行させることが可能
        - バックグラウンドタスクを開始するのではなく、フォアグラウンドの処理を中断せずに実行させ続けるもの
        - 主にユーザー起動のリクエストが当てはまる
    - アプリが停止する前にバックグラウンドで動く時間をAppに与えることができる
    - ユーザーによりアプリがkillされた場合は実行はキャンセルされる
- 実行可能時間
    - 最大実行時間については明記されていないが、環境によって変わるらしい
        - `UIApplication.shared.backgroundTimeRemaining` で取得できる
        - 検証環境（iPhone11Pro iOS13）では30secだった
    - 長時間に及ぶタスクには向かない
- 所感
    - メッセージ送信中にバックグラウンドにされた場合などで、送信処理が完了させるまで少しの実行時間を与えたい場合みたいなケースに有効

### イメージ
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41486/70677bc0-82d9-0190-8b94-fdf14a636f56.png"/>

# サンプルコード
https://github.com/chocoyama/BackgroundSamples/blob/master/BackgroundSample/Views/BackgroundTaskCompletionView.swift

### 実装概要

BackgroundTaskCompletionは `UIApplication.shared.beginBackgroundTask` で起動することができる。
これにより現在フォアグラウンドで動いている処理を、バックグラウンドに移行しても一定の時間動作させ続けることが可能となる。

また、処理の完了後は、 `endBackgroundTask` を呼び出すことで処理を停止させることができる。
これが呼び出されないままバックグラウンドでの最大実行可能時間を過ぎてしまった場合、beginBackgroundTaskに渡してある `expirationHandler` が呼び出される。
（不要なリソースが使用され続けることを防ぐために、endBackgroundTaskを適切に呼ぶことが推奨されている。）
ここで各種キャンセル処理やユーザーへのフィードバックなどを行うことになる。

細かい内容はサンプルコード内にコメントとして記載しているが、以下に重要な部分のみ抜粋。

```swift
let taskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
    self.cancellables.forEach { $0.cancel() }
    NotificationHelper.postLocalNotification(with: Message(body: "処理が完了しませんでした"))
})

sampleRepository.post(processTime: 10)
    .sink(receiveCompletion: { (result) in
        switch result {
        case .finished:
            break
        case .failure:
            NotificationHelper.postLocalNotification(with: Message(body: "処理が失敗しました"))
            UIApplication.shared.endBackgroundTask(taskIdentifier)
        }
    }) { _ in
        NotificationHelper.postLocalNotification(with: Message(body: "処理が成功しました"))
        UIApplication.shared.endBackgroundTask(taskIdentifier)
    }.store(in: &cancellables)
```


# 検証結果

下記の3パターンについて、サンプルコードを挙動を確認した。
検証端末： `iPhone11Pro iOS13.3.1` 

#### 1. フォアグランドで処理を開始しただけの場合 （サンプルコード①）

- 実行直後にバックグラウンドに移行した場合、バックグラウンド状態では処理が停止された
- ただし、すぐにフォアグラウンドに復帰させると、中断されていた処理が再開する挙動になった

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41486/16c84537-3f2c-2d32-33f9-7791de95a023.gif"/>

#### 2. beginBackgroundTaskを利用した場合 （サンプルコード②）

- 実行直後にバックグラウンドに移行しても、バックグラウンド状態で処理が継続された

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41486/9aa6cc7c-b5af-1b2e-ae80-5dde44d4fa91.gif"/>


#### 3. バックグラウンド処理の中でさらにバックグラウンド処理を開始した場合 （サンプルコード③）

- バックグラウンド処理の中で、再度バックグラウンド処理を開始しても、実行可能時間は延長されなかった

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41486/9b52febf-6fb6-d27d-f406-9c8b19f813cc.gif"/>

それぞれの処理に関する簡易図
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41486/54d712e9-744a-91c4-5c4a-e517215fcd85.png"/>

# 参考

- https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtask
- https://developer.apple.com/documentation/uikit/uiapplication/1623029-backgroundtimeremaining