スレッドとは?
- コンピューターのプログラムは基本的に1行ずつコードが実行されながら動作する。このようなプログラムの流れを「スレッド」(Thread: 「糸」)と呼ぶ。
シングルスレッドとマルチスレッド
- 大抵のプログラムはひとつの処理の流れを記述するが、そのことを「シングルスレッド」と呼ぶ。
- しかし、プログラムによっては処理効率を上げるなどして、複数の処理を並行して行うことがある。それを「マルチスレッド」と呼ぶ。
Dispatch Queueクラス
- Swiftにおいては、このスレッドを管理するためにDispatch Queueクラスが存在します。
キューの種類
- Dispatch Queueクラスには2種類が存在します。
mainQueue
- メインスレッドでタスクが実行されていく直列のキューです。UI部分は主にこのキューで管理されているので、処理後にUIを更新したい場合は、このキューに格納する必要があります。
global Queue
- システム全体で共有される直列のキューです。このキューは5種類の実行優先順位ごとにキューが管理されます。
- userInteractive
- ユーザーからの入力をインタラクティブ(対話的)に反映させるために即座に実行させる必要がある場合に指定する優先度
- userInitiated
- ユーザーからの入力を受けてから反映させる場合に指定する優先度
- default
- 優先度が指定されなかった場合のデフォルト値となる優先度
- 明示的に指定するのは非推奨らしい
- utility
- ユーザーが正確性を求めていないような処理を行う場合の優先度
- background
- バックグラウンドで行うような処理を行う場合の優先度
- userInteractive
なぜメインスレッドでないとUIはアップデートできないのだろうか?
- ここで僕が思った疑問としては、どうしてUIのアップデートは毎回メインスレッドで呼ぶ必要があるのだろうか?と思った。
- みなさんもUITableViewなどの
reload
を行う際に、よくメインスレッドで呼んでくださいとエラーメッセージが表示されたことがあるだろう。
スレッドセーフではないから
- 調べていくと、UIKitはそもそも
nonatomic
であり、スレッドセーフでないからという内容が出てきた。 - つまり、ある特定のスレッドが走っている際に、他のデータが競合してしまう可能性があるということだ。
- そうなってくると、例えばUITableViewの場合だと、
- もしTableViewのセルをあるバックグラウンドスレッドで削除した際に他のバックグラウンドスレッドでインデックスを処理した場合、データ競合を起こす可能性が出てくる。
- 他にも色々なケースが考えられうるだろう。
- このようなケースを考えた場合に、単純に一つのシングルスレッドで操作を行った方がいいという結論がでる。
そもそもどうしてUIKitはスレッドセーフではないのか?
- UIKitがスレッドセーフでない理由はパフォーマンスが落ちるからである。具体的に、UIKitの処理スピードが落ちるということだ。
どのようにUIKitはレンダリングされているのか?
レンダリングに必要となる要素
- このような内容を見た時に、そもそもどのようにUIKitがViewに描画されているのかが気になった。
- まずUIKitの描画のために以下のような要素があることを理解する必要がある
- UIKit:
- 全てのコンポーネントを保持しており、ユーザーのイベントを処理するが、描画自体はできない
- Core Animation:
- 全てのViewを表示し、アニメーションする
- Open GL ES:
- 2Dと3Dの描画を提供しているサーバー
- Core Graphics:
- 2Dの描画を提供しているサーバー
- Graphics Hardware:
- GPU
- UIKit:
Core Animation Pipeline
-
その上で、Core Animation Pipelineというものが存在し、このプロセスをたどって、レンダリングが可能となる。
-
以下がそのプロセスである。
- Commit Transaction:
- Viewのレイアウトや画像のデコードとフォーマット変換操作、ビューレイヤーのパックアップを行った上で、Render Serverへの送信を行う
- Render Server:
- Commit Transactionから送られてきた情報をDesirializeする。その上で、ViewのPropertyから描画命令を生成し、VSync Signalが来た時にOpenGLを呼び出す。
- GPU:
- GPUはVSync Signalを待って、そのSignalが呼び出された時にOpenGLを使い、ここで初めて描画を行う。
- Display:
- バッファからデータを受け取り、表示のためそのデータをスクリーンに送る。
- Commit Transaction:
-
ちなみに、
VSync Signal
とはこれらのパイプラインを同期するためのものである。アプリが復帰してレンダリングを開始する時間や画面を合成する時間、ディスプレイのリフレッシュ周期の動機を行う。この同期をによって、途切れがなくなりグラフィックスの視覚的なパフォーマンスが良くなる。
ここで本題に戻る
- ここまでの情報を読んでいただくとわかる通り、UIKitの描画には色々な工程が必要となっており、決してシンプルなものではない。むしろ複雑なものである。そうなってきた時に、UIKitをシングルスレッドの処理のみでレンダリングしているとどうしても処理に時間がかかってしまう。そのため、UIKitはマルチスレッドで処理されており、そのため僕たちがUIKitを使う際には
non-atomic
なUIKitを利用するため、常にシングルスレッドでの呼び出しが必要となる。(この場合はメインスレッドのこと)