 こんにちは。開発部第一グループの吉田です。
こんにちは。開発部第一グループの吉田です。
SwiftのCombineでreceive(on:options:)メソッドはPublisherから要素を受け取るSchedulerを指定するために使用されますが、コードレビューやブログ記事のサンプルでRunLoop.mainとDispatchQueue.mainがSchedulerとして使用されているのを見かけます。どちらもメインスレッドで実行されますが、これら二つの主な違いは何で、各々はいつ使用すべきなのかを見ていきたいと思います。
RunLoop.main
RunLoopはイベント、タイマー、その他の非同期タスクを管理し、効率的にそれらを処理するための仕組みです。プログラムが何もしていない間もアクティブな状態を保ち適切なタイミングで処理を実行します。RunLoop.mainはアプリケーションのメインスレッドに関連付けられたRunLoopを指し、ユーザーのタッチジェスチャーや画面更新などUI関連のイベントを処理します。
DispatchQueue.main
DispatchQueueはGrand Central Dispatch (GCD)フレームワークの一部で、並行処理や非同期処理を管理するために使用されます。DispatchQueue.mainはアプリケーションのメインスレッドに関連付けられたキューで、即座に実行される必要があるタスク(処理)をスレッドに割り当て実行し、UI更新などメインスレッド依存処理を安全に行うために使用されます。
RunLoop.mainとDispatchQueue.mainの比較
RunLoop.mainとDispatchQueue.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.mainとDispatchQueue.mainはどちらもメインスレッド上で動作しますが、RunLoop.mainはスケジュールされたタスクを処理するのに適していて、DispatchQueue.mainは即時のタスク実行に適しています。Combineのreceive(on:options:) で使用する際には、それぞれの違いや使用するべきケースを理解し、適切なものを選択する様にしましょう。