iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

Grand Central Dispatch (GCD) の理解を深める

GCD とは?

macOSiOS、watchOS、tvOS のマルチコアハードウェアでの並列実行タスクをするために提供されている言語機能で、開発者は DispatchQueue を使用してスレッドでの処理を管理することができます。また、GCD の Queue は追加されたタスクの順番ごとに処理を実行する FIFO(First In First Out) の性質を持っています。

一旦用語をまとめる

Process(プロセス)

アプリケーションの実行インスタンスであり、専用の仮想メモリ空間およびシステムのリソースが割り当てられる。また、プロセス内には複数のスレッドが含まれる。

Thread(スレッド)

プロセスが実行する処理などを格納した箱のようなもの。

Task(タスク)

スレッドに格納される具体的な処理の単位。

Queue(キュー)

アプリケーションに含まれるスレッドを管理するための高レベルな API で、Thread の作成・管理の役割を担っている。また、実際に処理される時にはシステムが効率的なタスクの実行を行うことで、キューはスレッドの管理に集中することができる。

Queue(キュー)の種類

言語で提供されているキューは主に3つあり、それぞれスレッドの管理範囲が異なります。

Main

DispatchQueue.main でアクセスできるキューで、スレッドはシステムから提供されています(メインスレッド)。また処理の実行は直列で行われていて、原則として UI の更新などはこのスレッドで行う必要があります。

    DispatchQueue.main.async {
        // 処理
    }

Global

DispatchQueue.global(qos:) でアクセスできるキューで、引数に DispatchQoS.QoSClass を指定してタスクを実行するスレッドを指定します。また、システムのタスクの優先度には highdefaultlowbackground の4つがあり、DispatchQoS.QoSClass の値はシステムが効率的にタスクを行う際の優先順位を決める判断材料としても使用されます。基本的には、開発者はどれが最も優先度が高いなどと考える必要がなく、それぞれのタスク内容に応じて、適切な DispatchQoS.QoSClass を指定する必要があります。それぞれのタイプの詳細については公式のドキュメントを参照するといいでしょう。また、このキューはシステムで共有され、それぞれ並列で処理の実行が行われます。

    DispatchQueue.global(qos: .background).async {
        // 処理
    }

Custom

DispatchQueue(label: "hoge") のように label 引数に任意の値を追加することで、独自のスレッドにタスクを追加することができます。また、直列また並列でのタスクの実行を選択することができます。基本的には attributes 引数で .concurrent を指定すると並列で実行され、指定がない場合は(Swift2以前は .serial で指定できたっぽい)、直列での実行になります。

    DispatchQueue(label: "hoge.hoge.hoge").async {
        // 処理
    }

同期・非同期

先で説明した、どのタイプのキューも 同期非同期 での実行を選択することができ、それぞれ sync(同期)async(非同期) ないで処理を書くことができます。また具体的な挙動としては下記のようになります。

  • 同期処理
    • スレッドにタスクを追加した時に、呼び出し元のスレッドはそのタスクの完了をまってから次の処理を行います。
  • 非同期処理
    • スレッドにタスクを追加した時に、呼び出し元のスレッドはそのタスクの完了を待たずに次の処理を行います。

メインスレッド内でバックグランドスレッドにタスクを追加して、同期非同期 でそれぞれ実行したサンプルが下記のようになります。

    // 同期処理
    print("a")
    DispatchQueue.global(qos: .background).sync {
        print("b")
    }
    print("c")

    // 結果
    // a
    // b
    // c

    // 非同期処理
    print("a")
    DispatchQueue.global(qos: .background).async {
        print("b")
    }
    print("c")

    // 結果
    // a
    // c
    // b

これらは、キューの種類の説明でも出てきた 直列並列 の言葉と混合しがちなので、スレッド間の実行順序に使われるのが 同期非同期 で、スレッド内の処理の実行順序に使用されるのが 直列並列 という認識で差し支えないかと思われます。

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com