
こんにちは!私の名前はKiryl Faminです。iOS開発者です。
この記事では、 Grand Central Dispatch (GCD)についてまとめて説明します。Swift Modern Concurrency が存在する現在では GCD は時代遅れのように思われるかもしれませんが、このフレームワークを使用するコードは、本番環境でもインタビューでも今後何年も登場し続けるでしょう。
今日は、GCD の基本的な理解にのみ焦点を当てます。キューとスレッドの関係など、マルチスレッドの重要な側面のみを詳細に検討します。これは、他の多くの記事では見落とされがちなトピックです。これらの概念を念頭に置くと、 DispatchGroup
、 DispatchBarrier
、セマフォ、ミューテックスなどのトピックを理解しやすくなります。
この記事は、初心者と経験豊富な開発者の両方に役立つでしょう。技術用語を多用せず、すべてをわかりやすい言葉で説明できるよう努めます。
スレッド– 基本的には、一連のシステム命令が配置され実行されるコンテナです。実際、すべての実行可能コードは何らかのスレッド上で実行されます。メイン スレッドとワーカー スレッドを区別します。
マルチスレッド– システムが複数のスレッドを同時に実行する機能。これにより、複数のコード ブランチを並行して実行できます。
Grand Central Dispatch (GCD) – スレッドの操作を容易にするフレームワーク (マルチスレッドの利点を活用)。主なプリミティブはタスクとキューです。
したがって、GCD は、並行して実行されるコードを簡単に記述できるツールです。簡単な例としては、メイン スレッドの UI 更新を妨げないように、重い計算を別のスレッドにオフロードすることが挙げられます。
タスク– 開発者によってグループ化された一連の命令。どのコードが特定のタスクに属するかは開発者が決定することを理解することが重要です。
例えば:
print(“GCD”) // a task
let database = Database() let person = Person(age: 23) // also a task database.store(person)
キュー– GCD の基本的なプリミティブであり、開発者が実行するタスクを配置する場所です。キューは、スレッド間でタスクを分散する役割を担います (各キューはシステムのスレッド プールにアクセスできます)。
基本的に、キューを使用すると、スレッドを直接管理するのではなく、コードをタスクに整理することに集中できます。タスクをキューにディスパッチすると、利用可能なスレッドで実行されます。多くの場合、このスレッドはタスクのディスパッチに使用されたスレッドとは異なります。
すべてのGIFのmp4バージョンが見つかります
メイン キュー– メイン スレッドでのみ実行されるキュー。シリアルです (詳細は後述)。
let mainQueue = DispatchQueue.main
グローバル キュー – システムによって提供される 5 つのキュー (優先度レベルごとに 1 つ) があります。これらは同時実行可能です。
let globalQueue = DispatchQueue.global()
カスタム キュー – 開発者が作成したキュー。開発者は、5 つの優先度とタイプ (シリアルまたは同時実行) のいずれかを選択します (デフォルトではシリアル)。
let userQueue = DispatchQueue(label: “com.kirylfamin.concurrent”, attributes: .concurrent).
サービス品質 (QoS) – キューの優先順位のシステム。タスクがキューに入れられるキューの優先順位が高いほど、より多くのリソースが割り当てられます。QoS レベルは全部で 5 つあります。
.userInteractive
– 最高の優先度。即時実行が必要で、メインスレッドで実行するのに適さないタスクに使用されます。たとえば、リアルタイムの画像レタッチを可能にするアプリでは、レタッチ結果を即座に計算する必要があります。ただし、メインスレッドで実行すると、常にメインスレッドで発生する UI の更新とジェスチャ処理 (たとえば、ユーザーがレタッチする領域に指をスライドすると、アプリは「指の下」に結果を即座に表示する必要があります) に干渉します。したがって、メインスレッドに負担をかけずに、できるだけ早く結果を取得します。
.userInitiated
– 高速なフィードバックを必要とするタスクの優先度ですが、対話型タスクほど重要ではありません。通常、タスクがすぐに完了せず、待機する必要があることをユーザーが理解しているタスク (サーバー要求など) に使用されます。
.default
– 標準の優先度。開発者がキューの作成時に QoS を指定しなかった場合、つまりタスクに特定の要件がなく、コンテキストから優先度を決定できない場合に割り当てられます (たとえば、.userInitiated 優先度のキューからタスクを呼び出すと、タスクはその優先度を継承します)。
.utility
– ユーザーからの即時のフィードバックは必要ないが、アプリの動作に必要なタスクの優先度。たとえば、サーバーとのデータの同期や、自動保存のディスクへの書き込みなどです。
.background
– 最も低い優先度。例としては、キャッシュのクリーニングがあります。
すべてのキューはシリアルキューまたはコンカレントキューに分類されます
シリアル キュー– 名前が示すように、タスクが次々に実行されるキューです。つまり、現在のタスクが終了した後にのみ次のタスクが開始されます。
同時実行キュー– これらのキューを使用すると、タスクを並行して実行できます。つまり、以前のタスクが完了したかどうかに関係なく、リソースが割り当てられるとすぐに新しいタスクが開始されます。開始順序のみが保証され (先にキューに入れられたタスクは後のタスクよりも先に開始されます)、完了順序は保証されないことに注意してください。
ここで注意すべき重要な点は、呼び出しスレッドに関連するタスクの実行方法について説明していることです。つまり、タスクを呼び出す方法によって、タスクをキューにディスパッチするスレッド内でイベントがどのように展開されるかが決まります。
async
)非同期呼び出しとは、呼び出しスレッドがブロックされない呼び出しです。つまり、キューに登録されたタスクの実行を待機しません。
DispatchQueue.main.async { print(“A”) } print(“B”)
この例では、メインスレッドからメインキューにprint("A")
タスクを非同期的にエンキューします (このコードは特定のキュー内にはないため、デフォルトでメイン スレッドで実行されます)。したがって、メインスレッドのタスクを待たずに、すぐに実行を続行します。この特定の例では、 print("A")
タスクがメインキューにエンキューされ、その後すぐにprint("B")
がメインスレッドで実行されます。メインスレッドは現在のコードの実行でビジー状態であるため (メイン キューのタスクはメイン スレッドでのみ実行できます)、現在のタスクprint("B")
最初に終了し、メインスレッドが解放された後にのみ、メインキューにエンキューされたタスクprint("A")
が実行されます。出力は BA です。
DispatchQueue.global().async { updateData() DispatchQueue.main.async { updateInterface() } Logger.log(.success) } indicateLoading()
メイン スレッドからデフォルトの優先度でタスクをグローバル キューに非同期的に追加するので、呼び出し元のスレッドはすぐに続行され、 indicateLoading()
を呼び出します。
しばらくすると、システムはタスクにリソースを割り当て、スレッド プールの空きワーカー スレッドでタスクを実行し、 updateData()
が呼び出されます。
updateInterface()
を含むタスクは、メイン キューに非同期的にエンキューされます。呼び出し元のワーカー スレッドは、タスクの完了を待たずに続行します。
タスクは非同期にキューに入れられるため、リソースがいつ割り当てられるかはわかりません。この場合、 updateInterface()
(メイン スレッド) とLogger.log(.success)
(ワーカー スレッド) のどちらが先に実行されるかは確実にはわかりません (ステップ 1 ~ 2 でもわかりません。メイン スレッドのindicateLoading()
とワーカー スレッドのupdateData()
のどちらが先に実行されるかはわかりません)。メイン スレッドは UI の更新、ジェスチャ処理、その他の基礎タスクの処理で忙しいですが、それでも常に最大限のシステム リソースを受け取ります。一方、ワーカー スレッドでの実行用のリソースもほぼ即時に割り当てられます。
このアニメーションでは、グローバルキューが空きワーカースレッドでタスクを実行していることに注意してください。
sync
)同期呼び出しとは、呼び出しスレッドが停止し、キューに登録されたタスクが完了するまで待機する呼び出しです。
let userQueue = DispatchQueue(label: "com.kirylfamin.serial") DispatchQueue.global().async { var account = BankAccount() userQueue.sync { account.balance += 10 } let balance = account.balance print(balance) }
ここでは、グローバル キューでタスクを実行しているワーカースレッドから、カスタム キューにタスクを同期的にエンキューして残高を増やします。現在のスレッドはブロックされ、エンキューされたタスクが終了するまで待機します。したがって、カスタム キューのタスクが増分を完了した後にのみ残高が印刷されます。
注: 上記のアニメーションでは、カスタムキューが空きワーカースレッドでタスクを実行します。
同期タスクのコンテキストでは、デッドロック (1 つまたは複数のスレッドが自分自身または互いの進行を無期限に待機する状態) について説明することが重要です。最も一般的な例は、メインスレッドから DispatchQueue.main.sync {} を呼び出すことです。
メインスレッドは現在のタスクの実行でビジー状態です。このタスク内で、何らかのコードを同期的に実行したいと考えています。そのため、同期呼び出しはメインスレッドをブロックします。タスクはメインキューに登録されますが、メインスレッドが現在のタスクの終了を待機してブロックされているため開始できません。また、メインキューのタスクはメインスレッドでのみ実行できます。最初はイメージしにくいかもしれませんが、重要なのは、 DispatchQueue.main.sync
でキューに登録されたタスクが現在のタスクの一部になり、現在のタスクの後にキューに登録されることを理解することです。その結果、スレッドは現在のタスクによって占有されているため開始できない現在のタスクの一部を待機します。
func printing() { print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
メイン スレッドがブロックされているため、メイン キューからのprint("B")
実行できないことに注意してください。
このセクションでは、これまでに習得したすべての知識を活用して、面接で遭遇する単純なコード ブロックから、並行プログラミングの理解を深める高度な課題まで、さまざまな複雑さの演習について説明します。これらすべてのタスクで問われるのは、「コンソールに何が出力されるか」です。
メイン キューはシリアルで、global() キューは同時実行であり、場合によっては特定の属性を持つカスタム キューが問題に含まれる可能性があることに留意してください。
まず、通常の難易度のタスク、つまり出力に不確実性が生じる可能性が低いタスクから始めます。これらのタスクは面接で最も多く出題されるタスクです。重要なのは、時間をかけて問題を注意深く分析することです。
すべての演習の完全なコードはここにあります。
タスク 1
print(“A”) DispatchQueue.main.async { print(“B”) } print(“C”)
print("A")
が実行されます。print("B")
のタスクは、メイン キューに非同期でエンキューされます。メイン スレッドがビジー状態であるため、このタスクはキュー内で待機します。print("C")
が実行されます。print("B")
が実行されます。
答え:ACB
タスク 2
print(“A”) DispatchQueue.main.async { print(“B”) } DispatchQueue.main.async { print(“C”) } print(“D”)
print("A")
が実行されます。print("B")
はメイン キューに登録されます。メイン スレッドが使用可能になるまでメイン キューは続きます。print("C")
のタスクは print("B") の後にキューに入れられ、待機します。print("B")
が実行されます。print("C")
タスクが実行されます。
答え:ADBC
いくつかの例では説明を少し簡略化し、システムが同期呼び出しの実行を最適化するという事実を省略していることをすぐに述べておきます。これについては後で説明します。
タスク 3
print(“A”) DispatchQueue.main.async { // 1 print(“B”) DispatchQueue.main.async { print(“C”) } DispatchQueue.global().sync { print(“D”) } DispatchQueue.main.sync { // 2 print(“E”) } } // 3 print(“F”) DispatchQueue.main.async { print(“G”) }
print("A")
はメインスレッドで実行されます。"F"
を出力します。print("G")
操作は、前のタスク (手順 1 ~ 3) の後にメイン キューに登録されます。print("B")
の実行が開始されます。print("C")
操作がメイン キュー (現在のタスクがまだ実行中で、 print("G")
キュー内でそれに続く) に追加されます。これは非同期で追加されるため、実行を待たずにすぐに進みます。print("D")
操作がグローバル キューにエンキューされます。この呼び出しは同期的であるため、グローバル キューがそれを実行するまで (使用可能な任意のワーカー スレッドで実行される可能性があります) 待機してから続行します。print("E")
操作がメイン キューに追加されます。この呼び出しは同期的であるため、タスクが完了するまで現在のスレッドをブロックする必要があります。ただし、メイン キューにはすでにタスクがあり、 print("E")
操作はそれらのタスクの後に最後に追加されます。したがって、 print("E")
を実行する前に、これらの操作を先に実行する必要があります。ただし、メイン スレッドは現在の操作の実行でまだビジー状態であるため、次のキュー操作に進むことができません。現在の操作の後に"G"
と"C"
を印刷する操作がなかったとしても、現在の操作 (手順 1 ~ 3) がまだ完了していないため、スレッドは続行できません。"G"
と"C"
を印刷する操作の後にキューに追加されるだけです。
答え:AFBD
代替回答(2番目の呼び出しがasync
場合): AFBDGCE
タスク 4
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”) serialQueue.async { // 1 print(“A”) serialQueue.sync { print(“B”) } print(“C”) } // 2
タスク (手順 1 ~ 2) は、カスタム シリアル キューに非同期的にエンキューされます (デフォルトでは、 .concurrent
属性を使用していないため、キューはシリアルです)。
"A"
が印刷されます。print("B")
の同期タスクがキューに登録されます。呼び出しは同期であるため、スレッドは実行を待機してブロックされます。print("B")
タスクを開始できず、デッドロックが発生します。
答え:A、デッドロック
この例では、メイン キューでもカスタム キューでも、どのシリアル キューでもデッドロックが発生する可能性があることを示しています。
タスク 5
前のタスクのシリアル キューを同時実行キューに置き換えてみましょう。
DispatchQueue.global().async { // 1 print("A") DispatchQueue.global().sync { print("B") } print("C") } // 2
"A"
が出力されます。print("B")
を実行するための同期呼び出しが行われ、タスクが完了するまで現在のワーカースレッドがブロックされます。print("B")
タスクが別のワーカー スレッドで実行されるのを待機します。"C"
が出力されます。答え:ABC
タスク 6
print("A") DispatchQueue.main.async { // 1 print("B") DispatchQueue.main.async { // 2 print("C") DispatchQueue.main.async { // 3 print("D") DispatchQueue.main.sync { print("E") } } // 4 } // 5 DispatchQueue.global().sync { // 6 print("F") DispatchQueue.global().sync { print("G") } } // 7 print("H") } // 8 print("I")
メインスレッドは"A"
を出力します。
非同期タスク (手順 1 ~ 8) は、現在のスレッドをブロックせずにメイン キューに登録されます。
メインスレッドは継続し、 "I"
を出力します。
その後、メイン スレッドが解放されると、メイン キューに登録されたタスクが実行を開始し、 "B"
を出力します。
別の非同期タスク (手順 2 ~ 5) がメイン キューに登録され、現在のスレッドはブロックされません。
現在のスレッドで実行を継続し、操作 6 ~ 7 の同期ディスパッチがグローバル キューに行われます。これにより、タスクが完了するまで現在の (メイン) スレッドがブロックされます。
操作 6 ~ 7 は別のスレッドで実行を開始し、 "F"
を出力します。
print("G")
操作はグローバル キューに同期的にディスパッチされ、完了するまで現在のワーカー スレッドをブロックします。
"G"
が出力され、この操作がディスパッチされたワーカー スレッドがブロック解除されます。
操作 6 ~ 7 が完了し、ディスパッチ元のスレッド (メイン スレッド) のブロックが解除され、 "H"
が出力されます。
操作 1 ~ 2 が完了すると、実行はメイン キュー内の次の操作 (操作 2 ~ 5) に移動し、開始されて"C"
が出力されます。
操作 3 ~ 4 は、スレッドをブロックせずにメイン キューに登録されます。
現在の操作 (2–5) が終了すると、次の操作 (3–4) の実行が開始され、 "D"
が出力されます。
print("G")
操作はメイン キューに同期的にディスパッチされ、現在のスレッドをブロックします。
その後、システムはメイン スレッドでprint("E")
操作が実行されるまで無期限に待機します。スレッドがブロックされているため、デッドロックが発生します。
答え:AIBFGHCD、デッドロック
中程度の難易度のタスクには不確実性が伴います。このような問題は面接でもまれに発生します。
タスク 7
DispatchQueue.global().async { print("A") } DispatchQueue.global().async { print("B") }
print("A")
現在のスレッドをブロックすることなく、グローバル キューに非同期的にエンキューされます。print("B")
をキューに入れる次のコマンドを実行する前であってもです。この特定のケースでは、次のタスクが最初にキューに追加され、その後にのみグローバル キューにリソースが割り当てられます。これは、メイン スレッドに最も多くのリソースが割り当てられ、メイン スレッドの次の操作が非常に軽量 (単にタスクを追加する操作) であるためです。実際には、グローバル キューのリソース割り当てよりも高速に実行されます。次のセクションでは、反対のシナリオについて説明します。print("B")
はグローバル キューに登録されます。"A"
を印刷するタスクが"B"
よりも早く開始される可能性がありますが、印刷はアトミック操作ではないため (出力がコンソールに表示される瞬間は操作の終わり近くです)、順序を保証することはできません。
答え: (AB)
括弧は、文字が AB または BA の任意の順序で出現する可能性があることを示します。
タスク8
print("A") DispatchQueue.main.async { print("B") } DispatchQueue.global().async { print("C") }
ここでは、「A」が最初に印刷されることだけが確実です。メイン キューのタスクとグローバル キューのタスクのどちらが速く実行されるかを正確に判断することはできません。
答え:A(BC)
タスク9
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } DispatchQueue.main.async { // 1 print(“B”) }
そして
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } print(“B”) // 1
一方では、どちらの場合もprint("B")
メイン スレッドで実行されます。また、グローバル キューにリソースが割り当てられるタイミングを正確に判断することはできません。そのため、理論的には、メイン スレッドで // 1 とマークされたポイントに到達する直前に"A"
が印刷される可能性があります。ただし、実際には、最初のタスクは常に AB として印刷され、2 番目のタスクは BA として印刷されます。これは、最初のケースでは、 print("B")
が少なくともメイン スレッドの次の RunLoop 反復 (または数反復後) で実行されるのに対し、2 番目のケースでは、 print("B")
メイン スレッドの現在の RunLoop 反復で実行されるようにスケジュールされているためです。ただし、順序は保証できません。
両方のタスクの答え: (AB)
タスク 10
print("A") DispatchQueue.global().async { print("B") DispatchQueue.global().async { print("C") } print("D") }
出力の始まりが"AB"
であることは明らかです。 print("C")
をキューに入れた後、リソースがいつ割り当てられるかを正確に判断することはできません。このタスクはprint("D")
前または後に実行される可能性があります。これは実際にも時々発生します。
答え:AB(CD)
タスク 11
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”, qos: .userInteractive) DispatchQueue.main.async { print(“A”) serialQueue.async { print(“B”) } print(“C”) }
繰り返しになりますが、カスタム キューで print("B") にリソースが割り当てられるタイミングを正確に判断することはできません。実際には、メイン スレッドに最高の優先度が与えられるため、通常は "C" が "B" の前に印刷されますが、これは保証されません。
答え:A(BC)
タスク 12
DispatchQueue.global().async { print("A") } print("B") sleep(1) print("C")
ここでは、1 秒間のスリープによってグローバル キューにリソースを割り当てるのに十分な時間が確保されるため、出力が BAC になることは明らかです。メイン スレッドがスリープによってブロックされている間 (本番環境では実行しないでください)、 print("A")
別のスレッドで実行されます。
答え: BAC
タスク 13
DispatchQueue.main.async { print("A") } print("B") sleep(1) print("C")
この場合、 print("A")
はメイン キューに登録されているため、メイン スレッドでのみ実行できます。ただし、メイン スレッドはコードの実行を継続します ( "B"
を印刷し、スリープし、 "C"
を印刷します)。その後でのみ、RunLoop はキューに登録されたタスクを実行できます。
答え:BCA
面接でこれらの問題に遭遇する可能性は低いですが、これらの問題を理解することで GCD をよりよく理解できるようになります。
ここでのCounterクラスは参照セマンティクスのためだけに使用されます。
final class Counter { var count = 0 }
タスク 14
let counter = Counter() DispatchQueue.global().async { DispatchQueue.main.async { print(counter.count) } for _ in (0..<100) { // 1 counter.count += 1 } }
ここでは、メイン スレッドのビジー状態に応じて、0 から 100 までの任意の数値が出力されます。ご存知のとおり、非同期タスクがリソースを取得するタイミングを正確に予測することはできません。ワーカー スレッドのループの前、最中、または後に発生する可能性があります。
答え: 0-100
タスク 15
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } DispatchQueue.global(qos: .userInteractive).async { print(“B”) }
QoS では、優先度の高いキューがリソースをより速く受け取ることが保証されませんが、iOS はそうしようとします。実際には、ここでの出力は (AB) です。
答え: (AB)
タスク 16
var count = 0 DispatchQueue.global(qos: . userInitiated).async { for _ in 0..<1000 { count += 1 } print(“A”) } DispatchQueue.global(qos: .userInteractive).async { for _ in 0..<1000 { count += 1 } print(“B”) }
どの実行が最初に開始されるかはわからないため、1000 の操作にわたっても、どのタスクが早く完了するかを判断することはできません。
答え: (AB)
タスク 16.2
操作が同時に実行を開始すると仮定した場合の出力はどうなりますか?
.userInteractive キューにはより多くのリソースが割り当てられているため、1000 操作の範囲で、そのキュー内の実行は常により速く終了します。
答え: BA
タスク 17
同様のアプローチを使用して、前のセクションの不確実性のあるタスク (たとえば、タスク 12) を変更できます。
let counter = Counter() let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”, qos: .userInteractive) DispatchQueue.main.async { serialQueue.async { print(counter.count) } for _ in 0..<100 { counter.count += 1 } }
0 から 100 までの任意の数値を印刷できます。0 を印刷できるという事実は、タスク 12 では"C"
の出力が常に"B"
の前に発生することを保証できないことを示しています。基本的に何も変わっていないためです。ループは印刷よりもわずかに多くのリソースを消費するだけです (実行前であってもループを開始するだけでは、実際には完全な不確実性が生じることに注意してください)。
答え: 0-100
タスク 18
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } print(“B”) DispatchQueue.global(qos: .userInteractive).async { print(“C”) }
ここでも同様の状況が発生します。理論上は、 print("A")
print("B")
よりも速く実行される可能性があります ( print("B")
少し重いものに置き換えた場合)。実際には、 "B"
常に最初に印刷されます。ただし、 print("C")
をキューに入れる前にprint("B")
を実行すると、メイン スレッドでprint("B")
に費やされる余分な時間は、多くの場合、.userInitiated キューがリソースを取得して print("A") を実行するのに十分であるため、 "A"
"C"
より前print("A")
される可能性が大幅に高まります。ただし、これは保証されておらず、 "C"
方が速く印刷される場合もあります。したがって、理論上は完全に不確実ですが、実際には B(CA) になる傾向があります。
答え: (BCA)
タスク 19
DispatchQueue.global().sync { print(Thread.current) }
同期状態に関するドキュメント:
「パフォーマンスの最適化のため、この関数は可能な限り現在のスレッドでブロックを実行しますが、1 つの例外があります。メイン ディスパッチ キューに送信されたブロックは常にメイン スレッドで実行されます。」
これは、最適化の目的で、同期呼び出しが、呼び出されたのと同じスレッドで実行される可能性があることを意味します ( main.sync
は例外で、これを使用するタスクは常にメイン スレッドで実行されます)。したがって、現在の (メイン) スレッドが出力されます。
回答:メインスレッド
タスク 20
DispatchQueue.global().sync { // 1 print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
デッドロックが発生したため、 "A"
のみが出力されます。最適化により、タスク (ラベル 1) がメイン スレッドで実行を開始し、 main.sync
を呼び出すとデッドロックが発生します。
答え:А、デッドロック
タスク 21
DispatchQueue.main.async { print("A") DispatchQueue.global().sync { print("B") } print("C") }
最適化により、 print("B")
タスクはキューに入れられるのではなく、現在の実行スレッドに「接合」されます。したがって、コードは次のようになります。
DispatchQueue.global().sync { print("B") }
次と同等になります:
print(“B”)
答え:ABC
これらのタスクから、main.sync は、呼び出しがメイン スレッドから行われていないことが確実な場合にのみ、非常に慎重に使用する必要があることがわかります。
この記事では、iOS のマルチスレッドの基本概念であるスレッド、タスク、キューとそれらの相互関係に焦点を当てました。GCD がメイン キュー、グローバル キュー、カスタム キュー全体でタスクの実行を管理する方法を調べ、シリアル実行と同時実行の違いについて説明しました。さらに、同期 (sync) タスク ディスパッチと非同期 (async) タスク ディスパッチの重要な違いを調べ、これらのアプローチがコード実行の順序とタイミングにどのように影響するかを強調しました。これらの基本概念を習得することは、応答性の高い安定したアプリケーションを構築し、デッドロックなどのよくある落とし穴を回避するために不可欠です。
この記事で何か役に立つ情報を見つけていただければ幸いです。不明な点があれば、Telegram: @kfamynまでお気軽にご連絡ください。無料でご説明いたします。
sync
メソッドのドキュメント - https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)-3segw