RunLoop は、特定のスレッドでの受信イベントの受信と処理を調整するループです。
RunLoop はすべてのスレッドに存在しますが、デフォルトではスタンバイ モードになっており、何も動作しません。
開発者は必要に応じて実行できますが、自動的には機能しません。これを行うには、コードを記述する必要があります。
まず、RunLoop は受信タスクのフローを管理し、適切なタイミングで実行するように設計されています。
これは、UIScrollView を使用する場合など、UI を操作する場合に最も顕著です。
デフォルトでは、メインの RunLoop は常にアプリケーション内で実行されます。システムからのメッセージを処理し、アプリケーションに送信します。このようなメッセージの例としては、ユーザーが画面をクリックしたときのイベントなどが挙げられます。
補助スレッドでは、RunLoop の必要性を自己決定する必要があります。必要な場合は、自分で設定して実行する必要があります。 RunLoop をデフォルトで実行することはお勧めできません。スレッドとのアクティブな対話が必要な場合にのみ必要です。
また、アプリケーション内のすべてのタイマーは実行ループ上で実行されるため、アプリケーション内でタイマーを操作する必要がある場合は、必ず実行ループの機能を検討する必要があります。
RunLoop はループであり、開発者が特定のタスクを実行するタイミングを理解するのに役立ついくつかの動作モードがあります。
したがって、RunLoop は次のモードになります。
Default
- デフォルト モードでは、ストリームは無料であり、大規模な操作を安全に実行できます。
Tracking
- スレッドは重要な作業でビジー状態です。この時点では、タスクを実行しないこと、または少なくともいくつかの小さなタスクを実行することをお勧めします。
Initialization
- このモードはストリームの初期化中に 1 回実行されます。
EventReceive
- これはシステム イベントを受信するための内部モードであり、通常は使用されません。
Common
- 実質的な意味を持たないプレースホルダー モードです。
メインの RunLoop では、これらのモードは自動的に切り替わります。開発者はこれらを使用して、時間のかかるタスクを実行することで、ユーザーがインターフェイスのハングに気付かないようにすることができます。例を見てみましょう。
他の RunLoop での実行サイクル管理は完全には自動ではありません。適切なタイミングで実行サイクルを開始するスレッドのコードを作成する必要があります。また、イベントに適切に応答し、無限ループを使用して実行サイクルが停止しないようにする必要があります。
UIScrollView があり、ユーザーが何も気付かないようにメインスレッドで大規模なタスクを実行する必要があります。
通常の方法でタスクを完了できます。
DispatchQueue.main.async { sleep(2) self.tableView.refreshControl?.endRefreshing() }
しかし、結果はかなり悪いものになるでしょう。ユーザーはアプリケーションの大幅な遅延に気づくでしょう。
この悪影響は、現在タスクで何が起こっているかに注意を払わずにメインスレッドでタスクを実行するという事実によって発生します。
このため、ユーザーがインターフェイスを操作した瞬間に、私たちは大きなタスクの実行を開始します。もちろん、これはユーザーにとってインターフェイスがハングしているように見えるという事実につながります。
これは、RunLoop メカニズムを使用することで回避できます。これを使用して同じロジックを実装してみましょう。
CFRunLoopPerformBlock(CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue) { sleep(2) self.tableView.refreshControl?.endRefreshing() }
ここで何が起こるかを説明しましょう。 CFRunLoopPerformBlock 関数は、RunLoop を通じて実行するコードを追加します。コード ブロック自体に加えて、この関数には 2 つの重要なパラメーターがあります。
最初の関数は、どの RunLoop が関数を実行するかを選択する役割を果たします。この例では「main」が使用されています。
2 つ目は、タスクが完了するモードを担当します。
合計 3 つのモードが可能です。
共通 - デフォルト モードと追跡モードを組み合わせます。
上記のコードを使用してプログラムを実行すると、結果は次のようになります。
ユーザーがユーザー インターフェイス (UI) との対話を開始すると、メイン実行ループは「追跡」モードに切り替わり、インターフェイスのスムーズさを確保するために、他のすべてのイベントの処理を一時的に停止します。ユーザーがインターフェイスとの対話を停止すると、実行ループは「デフォルト」モードに戻り、タスクの実行を再開します。
ユーザー インターフェイスに加えて、ループはタイマーの機能とも密接にリンクしています。
アプリケーション内のタイマーはすべてループ内で実行されるため、特に支払い処理などの重要な機能を担当している場合は、タイマーの操作中に間違いを犯さないように特に注意する必要があります。
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething }
タイマーはデフォルトではデフォルト モードで開始するため、ユーザーがその時点でテーブルをスクロールしている場合は動作が停止する可能性があります。これは、ループが現在追跡モードになっているためです。このため、例のコードが正しく動作しない可能性があります。この問題は、コモン モードでタイマーを追加することで解決できます。
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething } RunLoop.main.add(timer, forMode: .common)
また、タイマーが予期した時間に起動しない場合や、まったく起動しない場合もあります。これは、RunLoop が各サイクルの開始時にのみタイマーをチェックするためです。 RunLoop がこのステージを通過した後にタイマーがトリガーされた場合、次の反復が開始されるまでそのことはわかりません。同時に、RunLoop でのタスクの実行時間が長ければ長いほど、遅延も長くなります。
この問題を解決するには、新しいスレッドを作成し、そのスレッドで RunLoop を開始し、他のタスクを追加せずにスレッドにタイマーを追加します。これにより、タイマーが正しく機能します。
let thread = Thread { let timer = Timer(timeInterval: 1.0, repeats: true) { timer in // makeSomething } RunLoop.current.add(timer, forMode: .default) RunLoop.current.run() } thread.start()
この記事では、RunLoop とは何か、そしてそれが iOS アプリケーションでどのような問題を解決するのかについて説明しました。 RunLoop は、特定のスレッド内で受信イベントの受信と処理を調整するループであり、いくつかの動作モードがあります。
これは、適切なタイミングでタスクを実行できるため、ユーザー インターフェイス (UI) とタイマーを操作する場合に特に便利です。これにより、インターフェイスの「ハングアップ」を回避し、タイマーの正しい動作を保証できます。
実行ループを使用するには追加のコーディングが必要ですが、アプリケーションの効率と安定性を向上させる価値のある投資です。