アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

Swift CombineでのRunLoop.mainとDispatchQueue.main

こんにちは。開発部第一グループの吉田です。

SwiftのCombineでreceive(on:options:)メソッドはPublisherから要素を受け取るSchedulerを指定するために使用されますが、コードレビューやブログ記事のサンプルでRunLoop.mainDispatchQueue.mainがSchedulerとして使用されているのを見かけます。どちらもメインスレッドで実行されますが、これら二つの主な違いは何で、各々はいつ使用すべきなのかを見ていきたいと思います。

RunLoop.main

RunLoopはイベント、タイマー、その他の非同期タスクを管理し、効率的にそれらを処理するための仕組みです。プログラムが何もしていない間もアクティブな状態を保ち適切なタイミングで処理を実行します。RunLoop.mainはアプリケーションのメインスレッドに関連付けられたRunLoopを指し、ユーザーのタッチジェスチャーや画面更新などUI関連のイベントを処理します。

DispatchQueue.main

DispatchQueueはGrand Central Dispatch (GCD)フレームワークの一部で、並行処理や非同期処理を管理するために使用されます。DispatchQueue.mainはアプリケーションのメインスレッドに関連付けられたキューで、即座に実行される必要があるタスク(処理)をスレッドに割り当て実行し、UI更新などメインスレッド依存処理を安全に行うために使用されます。

RunLoop.mainとDispatchQueue.mainの比較

RunLoop.mainDispatchQueue.mainの違いをまとめると以下の様になります。

RunLoop.main DispatchQueue.main
スレッド メインスレッド メインスレッド
目的 イベント処理、タイマー、ユーザーの入力イベントの処理 メインスレッドでのタスク実行
実行タイミング 次のランループサイクル 即座に実行
適した用途 タイマーやスケジュールされたタスクの処理 UI更新やメインスレッドでのタスク実行

receive(on:options:) における RunLoop.main と DispatchQueue.main の違い

Combineのreceive(on:options:)にこれらのスケジューラーを使用した場合の違いを見ていきたいと思います。

以下の様にSwiftUIのListで各itemが表示された時にitem内のデータを取得する実装を行っていたとします。

List {
    ForEach(Array(viewModel.state.list.enumerated()), id: \.offset) { index, element in
        item(index, element)
            .onAppear {
                viewModel.fetchItemData(index)
            }
    }
}
func fetchItemData(_ index: Int) {
    fetchData(index)
        .receive(on: RunLoop.main, options: nil) // もしくは"DispatchQueue.main"
        .sink { _ in
            print("Completed")
        } receiveValue: { data in
            self.setData(index, data) // item内のデータを設定
        }
        .store(in: &cancellables)
}
  • RunLoop.mainの場合

    タスクが次のランループサイクルにスケジュールされるためスクロール中は更新がされません。スクロール後にスケジュールされたタスクが処理されています。  

        

  • DispatchQueue.mainの場合

    スクロール中も即座にタスクが実行され更新されています。  

        

Combine receive(on:options:)での適切な使い分け

  • DispatchQueue.main を使用すべきケース
    • UI の更新やユーザーインタラクションの処理などメインスレッドでの即時実行が必要な場合。
  • RunLoop.main を使用すべきケース
    • イベントサイクルの一部としてタスクを実行する必要がある場合。
    • タスクの実行にランループの正確なタイミングが必要な場合。
  • RunLoop.mainを使用する際の注意点
    • タスクが次のランループサイクルにスケジュールされるため、UI の更新やイベント処理が遅れることがある。
    • 別のタスクの完了を待っている場合ランループ内でデッドロックが発生する可能性がある。

まとめ

RunLoop.mainDispatchQueue.mainはどちらもメインスレッド上で動作しますが、RunLoop.mainはスケジュールされたタスクを処理するのに適していて、DispatchQueue.mainは即時のタスク実行に適しています。Combineのreceive(on:options:) で使用する際には、それぞれの違いや使用するべきケースを理解し、適切なものを選択する様にしましょう。

参考記事