LoginSignup
4
3

More than 5 years have passed since last update.

SwiftQueue を使ってみた

Last updated at Posted at 2019-01-12

はじめに

SwiftQueuelucas34 氏が公開している iOS 向けの job scheduler ライブラリです。

job の実行条件を指定して、

  • 実行条件を満たしたときに自動で再実行
  • 成功するまで一定間隔でリトライ

などが可能です。
Android の JobSchedulerWorkManager の WorkRequest.Builder とよく似ていています。
(実は Android のこれらと同等の iOS ライブラリを探していて、 SwiftQueue を見つけました)

使いどころ

  • リトライ制御
  • モバイルデータ通信ではなく Wi-Fi 接続時にのみ行いたい処理の実行(写真等の同期)
  • 定期実行処理(新着確認のポーリング)

通信完了まで画面をブロックしない アプリを提供するためには有用なライブラリだと見込んでいます。
詳しくは インドのインターネット環境との戦い方 参照

続編で、 SwiftQueue の内部を追ってみた もあります。

詳細

※Github の SwiftQueue より抜粋・補足

機能

  • 逐次実行
  • 並列実行
  • キャンセル(すべて・ID 指定・タグ指定)
  • 遅延実行
  • 実行条件の指定(期日・インターネット接続・充電中)
  • 先に同じ処理が実行されている場合の動作の選択
  • リトライ(回数・一定時間後・exponential backoff:指数関数的後退)
  • 定期実行

使い方

Job の定義

SendTweetJob.swift
// Tweet を送信する Job
class SendTweetJob: Job {

    // この Job の識別子:JobCreator で job を生成する際に使用
    static let type = "SendTweetJob"
    // パラメータ
    private let tweet: [String: Any]

    required init(params: [String: Any]) {
        // JobBuilder.with() で指定されたパラメータを受け取る
        self.tweet = params
    }

    func onRun(callback: JobResult) {
        // 処理の実行
        let api = Api()
        api.sendTweet(data: tweet).execute(onSuccess: {
            // 成功したとき .success を callback に渡す
            callback.done(.success) 
        }, onError: { error in
            // 失敗したとき .fail を callback に渡す
            callback.done(.fail(error))
        })
    }

    func onRetry(error: Error) -> RetryConstraint {
        // エラー種別から、キャンセル or リトライを決める
        return error is ApiError ? RetryConstraint.cancel : RetryConstraint.retry(delay: 0)
    }

}

JobCreator の定義

TweetJobCreator.kt
class TweetJobCreator: JobCreator {

    // type に応じて、実際の Job インスタンスを生成して返す
    func create(type: String, params: [String: Any]?) -> Job {
        if type == SendTweetJob.type  {
            return SendTweetJob(params: params)
        } else {
            // 合致しないときはエラーを返す
            fatalError("No Job !")
        }
    }
}

呼び出し

// job のキャンセルをしたい場合は、同じ manager インスタンスからキャンセルする必要がある
let manager = SwiftQueueManagerBuilder(creator: TweetJobCreator()).build()
JobBuilder(type: SendTweetJob.type)
        .internet(atLeast: .cellular) // インターネット接続を実行条件に指定
        .with(params: ["content": "Hello world"]) // パラメータ指定
        .schedule(manager: manager) // 実行予約(enqueue)

これらの実行を行うと、呼び出しは即時完了します。
(※ enqueue するだけで、投稿処理は完了していません!)

インターネット接続がある場合だと、job はすぐに実行されます。
インターネット接続がない場合は、job は保留され、インターネット接続が確保できれば自動で再実行されます。

試してみた機能

このコードをもとに試します。

let manager = SwiftQueueManagerBuilder(creator: MyJobCreator()).build()
for i in 1..<10 {
    JobBuilder(type:  MyJob.type)
        .with(params: ["key": "\(i)"])
        .schedule(manager: manager)
}
MyJob.swift
func onRun(callback: JobResult) {
    print("key = \(params?.first?.value as! String)") // パラメータの数字を print
    Thread.sleep(forTimeInterval: 1) // 1秒待つ
    callback.done(.success) // 成功
}

func onRemove(result: JobCompletion) {
    print("onRemove \(params?.first?.value as! String) is \(result)")
}

逐次実行

デフォルトでは1本の queue だけで実行される。

実行結果

key = 1
 (1秒後)
key = 2
 (1秒後)
key = 3
 (1秒後)
key = 4
 (1秒後)

並列実行

.group(name:) を指定するとその名前のグループの queue で実行されます。

JobBuilder(type:  MyJob.type)
    .group(name: "group\(i % 3)") // 3つのグループに振り分ける
    .with(params: ["key": "\(i)"])
    .schedule(manager: manager)

実行結果

key = 1
key = 2
key = 3
 (1秒後)
key = 5
key = 6
key = 4
 (1秒後)
key = 8
key = 9
key = 7

3並列で実行されているのがわかります。
(同時スタートなので、 5,6,4 などは順不同になる)

先に同じ処理が実行されている場合の動作の選択

job に ID を指定することで「同じ処理か」を識別する。
先の処理を上書きするか否かの選択ができる。

JobBuilder(type:  MyJob.type)
    .singleInstance(forId: "id\(i % 3)", override: false)
    .group(name: "group\(i % 3)")
    .with(params: ["key": "\(i)"])
    .schedule(manager: manager)

実行結果(override:false)
各 group 内で先勝ちになっています。

key = 1
key = 2
key = 3
onRemove 4 is fail(SwiftQueue.SwiftQueueError.duplicate)
onRemove 5 is fail(SwiftQueue.SwiftQueueError.duplicate)
onRemove 6 is fail(SwiftQueue.SwiftQueueError.duplicate)
onRemove 7 is fail(SwiftQueue.SwiftQueueError.duplicate)
onRemove 8 is fail(SwiftQueue.SwiftQueueError.duplicate)
onRemove 9 is fail(SwiftQueue.SwiftQueueError.duplicate)

実行結果(override:true)
各 group 内で後勝ちになっています。

key = 1
key = 2
key = 3
key = 6
key = 5
key = 4
key = 7
key = 8
key = 9
onRemove 5 is fail(SwiftQueue.SwiftQueueError.canceled)
onRemove 1 is fail(SwiftQueue.SwiftQueueError.canceled)
onRemove 6 is fail(SwiftQueue.SwiftQueueError.canceled)
onRemove 3 is fail(SwiftQueue.SwiftQueueError.canceled)
onRemove 2 is fail(SwiftQueue.SwiftQueueError.canceled)
onRemove 4 is fail(SwiftQueue.SwiftQueueError.canceled)

雑感など

  • iOS アプリの job 実行管理がかなり便利になりそう
  • group は Producer-Consumer のように、何本かの group を確保して、処理が終わった group に割り振るということはできなさそう…
  • 画面をまたいだ場合や、アプリのライフサイクル(バックグラウンドの行き来や終了&起動)時に自動復帰するのか要確認
  • 条件が満たされたときの自動実行は、充電なら NotificationCenterUIDevice.batteryStateDidChangeNotification を指定し、通信は Reachability ライブラリを使っていた
  • 永続化されているみたいだけど、 Android の WorkKamanger のように クラス名をリファクタリングしたときの影響は…?

もうちょっと色々試してみます。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3