くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

iOSにおける同期・非同期処理について

今回はモバイル開発で欠かせない同期・非同期処理の概念についてです

最初の方の概念の説明はAndroidも共通している内容ですが、後半はiOSに特化した内容になります

Androidの同期・非同期処理はこの前、技術書典で出版した本で軽く説明しているので気になる方は読んでみてください!!(今回はAndroidの解説は省きます)

同期・非同期とは?

まずは、同期・非同期処理とはいったい何なのかを説明しようと思います

同期

同期処理のイメージは次のようなものです

同期処理のイメージ

タスクA、タスクBを同期処理するアプリケーションがあったとします。このアプリケーションが、ユーザーAからリクエストを受けた場合を例にします

プログラムに書かれた通りの順番でタスクが処理されるので、タスクBが終わるまでタスクAの処理が中断され、ユーザーからは画面が固まったように見えてしまいます。

非同期

非同期処理とは、あるタスクを実行している最中に、その処理を止めることなく別のタスクを実行できる方式です

処理の流れは次のようなイメージです

非同期処理のイメージ

非同期処理の場合は、実行の順番を待たないため、処理が完了する順序はその都度異なります

非同期処理は、上図のように、ユーザーAのリクエストを処理中にユーザーBからのリクエストがあっても、ユーザーBはユーザーAの処理完了を待たずに、結果を受け取ることができます

非同期処理をうまく活用すると、全体の処理速度を速められるメリットがあります

このとき注意しておきたいのは、非同期処理と並行処理の意味です。並行処理とは文字どおり複数の処理を同時進行で行うことです

一方、非同期処理は処理を止めることなく実行できるというだけです

  • 並行処理:複数の処理を1つの主体が切り替えながらこなすこと
  • 並列処理:複数の処理を複数の主体で同時にこなすこと
  • 同期処理:複数の処理をこなす際、ある処理が別の処理の終了を待つような処理
  • 非同期処理:複数の処理をこなす際、ある処理は別の処理の終了を待たないような処理

iOSで非同期を行うためには?

では、iOSで非同期を行うためにはどうすればいいでしょうか?

iOSにおける並列(非同期)処理の手法には次の手法があります。最近だとasync/awaitが登場して一番使いやすいと思いますがここでは割愛します

  • Thread : スレッドを立ててスレッドの中で処理を行う。スレッドの管理やキューイングなどの管理はアプリケーションが行う
  • GCD(Grand Central Dispatch) : スレッドの管理などをOSレベルで実装したもの。処理をしたいタスクをClosureで渡す。渡されたタスクはQueue(キュー)に挿入されて逐次実行される

この中で、GCDがよく利用されています。Threadだとスレッドの処理などを自前で書かないといけないのでコードが冗長になってしまい大変です

そこで今回は、GCDについて解説を進めて行きます

GCDの仕組みを簡単に説明すると次のような仕組みです

  • DispatchQueueというキューに処理を行いたい内容(タスク)を追加する(GCDではQueueのことをDispatchQueueとも呼びます)
  • キューに追加されたタスクはFIFO(First In First Out)で順番に実行される
  • タスクの実行は別スレッド上で実行される
  • 処理の開始自体はQueueに追加された順に実行されます。

そして次のような特徴があります

  • タスクはClosureとして登録して、キューの管理はアプリケーション独自に作ったもの、アプリケーション起動時にシステム内で自動的に生成されるもの、の二種類がある
  • 適切なスレッドへのタスクの振り分けはOSが行う
  • アプリケーション内部で自動生成されたキューと、そのキューに追加されたタスクを実行するスレッドの管理はOSが自動的に行ってくれる

スレッドの生成や管理はシステムがやってくれるので重要なことは、実行したいタスクを定義して適切なDispatchQueueに追加することです 

次の処理で追加します

関数 補足
func async(group: DispatchGroup? = default, qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, execute work: @escaping () -> Void) Closureで定義された処理をqueueに追加する。タスクの処理は非同期で実行される
func sync(execute block: () -> Swift.Void) Closureで定義された処理をqueueに追加する。タスクの処理は同期的に実行される

asyncやsyncは次のようにして使います

DispatchQueue.global.sync {
}

DispatchQueue.main.async {
}

DispatchQueueにはキューが次のように存在します。

キューの種類 説明
serial dispatch queue(直列 ) タスクを同時に一つずつ追加された順に実行する仕組み。タスクは他のキューと独立したスレッド上で動作
concurrent dispatch queue(並列) 複数のタスクを同時に実行する。実行の順番はキューに追加した順番になるが、終了のタイミングの順序は保証されない。同時に実行するタスクの数はシステムの状況に応じて変化する。アプリケーションが所有するglobalQueueとアプリケーション内部で生成するqueueを持つことができる。キューには優先度をつけることができる
main dispatch queue アプリケーションのどこからでも利用することの出来るシリアルキュー(直列)で、アプリケーションのメインスレッド上で実行される。UIの更新などはこのキューを用いて行う必要がある

globalQueueは上記の表にもあるように実行優先度ごとにキューが管理されます

優先度 説明
優先度1:userInteractive ユーザーの入力に対してインタラクティブに実行。即時に実行されなければフリーズしているように見える処理に使う
優先度2:userInitiated ユーザーの入力を受けて実行される処理に使う
優先度3:default 優先度が指定されていない場合に指定される。明示的には指定しない
優先度4:utility プログレスバー付きのダウンロードなどに利用される。視覚的な情報の更新をするが、即時の結果を要求しない処理に使う
優先度5:background バックアップなどの見えないところで使われる。時間がかかっても問題ない処理に使う

例えば次のように使用します

let globalQueque = DispatchQueue.global(qos: .background)
globalQueque.async {
}

最後にThread.isMainThreadで、現在の処理がメインスレッドで実行されているのかを確認することができます。 スレッドのして方法に誤りがあってクラッシュすることもあるので、現在メインスレッドなのかを確認するために使います

そしてここまで説明した概念を元にConcurrencyを勉強しましょう!

参照

qiita.com

dev.classmethod.jp

developer.apple.com

developer.apple.com

developer.apple.com

qiita.com