Swiftでのマルチスレッドの実装!初心者のための10選サンプルコード

Swiftを使ったマルチスレッドのサンプルコードイメージSwift
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めば、Swiftでマルチスレッドを実装する方法がマスターできるようになります。

Swift言語を使用して、効率的にマルチスレッドを実装する方法を初心者目線で解説します。

10の詳細なサンプルコードとともに、その応用例、注意点、カスタマイズ方法を紹介します。

マルチスレッドは複数の処理を同時に行うためのテクニックであり、アプリのパフォーマンスを大幅に向上させることができます。

しかし、その実装は難しく感じるかもしれません。

そんなあなたのために、この記事は準備されました。

私たちが一緒に学ぶことで、Swiftでのマルチスレッドの実装が一層楽しく、効果的になるでしょう。

●Swiftとマルチスレッドとは

SwiftはAppleが開発したプログラミング言語で、iOS、macOS、watchOS、tvOSなどのアプリ開発に使用されます。

Swiftは安全性とパフォーマンスを重視して設計されており、初心者からプロの開発者まで幅広く利用されています。

○Swiftの基本

SwiftはCやObjective-Cとは異なる新しい文法を持つ言語です。

それにもかかわらず、Appleの開発環境Xcodeとシームレスに統合されているため、簡単にiOSやmacOSのアプリを開発することができます。

Swiftは型安全性を提供し、エラーを防ぐ設計がされています。これにより、バグを減少させることが期待されています。

○マルチスレッドの概要

マルチスレッドとは、複数の処理を同時に行うための技術です。

コンピュータは複数のCPUコアを持っていることが多く、これらのコアを効果的に利用することで、アプリのレスポンスを向上させたり、処理速度を高めることができます。

しかし、マルチスレッドを適切に実装しないと、データの不整合や処理の競合が生じることがあるため、注意が必要です。

Swiftでは、Grand Central Dispatch (GCD) や OperationQueue などのフレームワークを使って、簡単にマルチスレッドの処理を実装することができます。

これらのフレームワークを利用することで、効率的な並行処理を行うことができるようになります。

●Swiftでのマルチスレッドの実装方法

Swiftでのマルチスレッド実装は、アプリのパフォーマンス向上に不可欠です。

マルチスレッドを適切に利用することで、アプリはスムーズに動作し、ユーザー体験が大幅に向上します。

では、どのようにSwiftでマルチスレッドを実装するのでしょうか。

ここからは具体的な方法と、それを理解するためのサンプルコードを紹介していきます。

○サンプルコード1:基本的なマルチスレッドの実装

最初に、Swiftで最も基本的なマルチスレッドの実装方法を紹介します。

このコードでは、DispatchQueueを使って非同期に処理を行います。

import UIKit

// メインスレッド
DispatchQueue.main.async {
    // UIの更新など、メインスレッドでのみ許される処理をここに記述
}

// バックグラウンドスレッド
DispatchQueue.global().async {
    // 重い処理や、時間がかかる処理をここに記述
}

このコードでは、メインスレッドでUIの更新を行い、バックグラウンドスレッドで重たい処理を実行しています。

メインスレッドはUIの更新専用のスレッドであり、重たい処理をするとアプリが固まってしまうため、バックグラウンドスレッドを活用することが重要です。

このコードを実行すると、メインスレッドとバックグラウンドスレッドがそれぞれの役割に従って動作することが確認できます。

○サンプルコード2:GCD(Grand Central Dispatch)の使用例

Grand Central Dispatch(GCD)は、非同期処理を簡単に実装できるAppleの技術です。

下記のコードでは、GCDを用いて非同期のタスクを実行しています。

import UIKit

let queue = DispatchQueue(label: "com.example.myqueue")

queue.async {
    for i in 1...5 {
        print("🟢 \(i)")
        sleep(1)
    }
}

DispatchQueue.main.async {
    for i in 1...5 {
        print("🔴 \(i)")
        sleep(2)
    }
}

このコードでは、新しくキューを作成し、そのキュー上で非同期にタスクを実行しています。

また、メインスレッド上でもタスクを実行しています。

このコードを実行すると、🟢🔴が交互に表示されることが確認できます。

これにより、複数のタスクが同時に実行されていることがわかります。

○サンプルコード3:OperationQueueの使用例

OperationQueueは、Swiftで非同期処理を管理するための強力なツールです。このツールを使用すると、複数の非同期タスクをキューに追加し、それらのタスクの実行順序や同時に実行できるタスクの数などを制御することができます。

OperationQueueは、タスクをOperationオブジェクトとして管理します。こちらは、OperationQueueの基本的な使用例を示すサンプルコードです。

import Foundation

// OperationQueueのインスタンスを作成
let operationQueue = OperationQueue()

// Operationオブジェクトを作成
let operation1 = BlockOperation {
    for i in 1...5 {
        print("タスク1: \(i)")
        sleep(1)
    }
}

let operation2 = BlockOperation {
    for i in 1...5 {
        print("タスク2: \(i)")
        sleep(1)
    }
}

// タスクをキューに追加
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)

このコードでは、2つのタスク(operation1とoperation2)をOperationQueueに追加しています。これらのタスクは非同期に実行されます。

このコードを実行すると、タスク1タスク2が交互に出力されることが確認できます。これにより、OperationQueueが複数のタスクを同時に管理していることがわかります。

OperationQueueの強力な機能の一つに、タスクの依存関係を管理することができる点があります。例えば、operation2がoperation1の完了後にのみ実行されるようにしたい場合、次のように依存関係を設定できます。

operation2.addDependency(operation1)

このようにして、タスク間の依存関係を制御することで、複雑な非同期処理のフローを簡単に実装することができます。

○サンプルコード4:マルチスレッドでのデータの共有

マルチスレッド環境では、複数のスレッドが同時にデータにアクセスする可能性があります。

このとき、データの整合性を保つための方法として、ロックの概念を使用することが一般的です。

Swiftでは、DispatchSemaphoreを使ってロックの機能を実装することができます。

import Foundation

var counter = 0
let semaphore = DispatchSemaphore(value: 1)

// スレッド1
DispatchQueue.global().async {
    for _ in 1...1000 {
        semaphore.wait()
        counter += 1
        semaphore.signal()
    }
}

// スレッド2
DispatchQueue.global().async {
    for _ in 1...1000 {
        semaphore.wait()
        counter -= 1
        semaphore.signal()
    }
}

このコードでは、2つのスレッドが同時にcounter変数を更新しています。

しかし、DispatchSemaphoreによって一度に1つのスレッドのみがcounterを更新できるようになっているため、データの不整合が起きることはありません。

このコードを実行すると、スレッド1がcounterを1000回増加させ、スレッド2がcounterを1000回減少させるため、最終的なcounterの値は0になります。

○サンプルコード5:非同期処理の基本

Swiftでは、非同期処理を簡単に実装するための多くのツールやライブラリが提供されています。

非同期処理は、重たい処理をバックグラウンドで実行し、メインスレッド(UIスレッド)をブロックしないようにするためのものです。

こちらは、Swiftで非同期処理を実装する基本的な方法を表すサンプルコードです。

import Foundation

// 非同期に実行するタスク
DispatchQueue.global().async {
    // 重たい処理を実行
    sleep(3)

    // 処理が完了したらメインスレッドに戻る
    DispatchQueue.main.async {
        print("非同期処理が完了しました")
    }
}

このコードを実行すると、sleep(3)によって3秒間の処理がバックグラウンドで実行されます。

その後、print関数を使用してメインスレッドに戻り、結果を出力します。

●Swiftのマルチスレッドの応用例

Swiftのマルチスレッドプログラミングは基本的な非同期処理だけでなく、さまざまな応用的なシナリオで使用されます。

今回は、その応用例を幾つかのサンプルコードとともに紹介します。

○サンプルコード6:非同期処理でのUI更新

Swiftでは、UIの更新は主にメインスレッドで行う必要があります。

しかし、非同期タスクの完了後にUIを更新したい場合も多々あります。

その際は、非同期タスクの終了後にメインスレッドに切り替えてUIを更新することが推奨されます。

import UIKit

let label = UILabel()

DispatchQueue.global().async {
    // ここで何らかの非同期処理
    sleep(2)

    DispatchQueue.main.async {
        label.text = "非同期処理完了!"
    }
}

上記のコードでは、バックグラウンドで非同期処理を行った後、メインスレッドに切り替えてUILabelのテキストを更新しています。

この方法を用いることで、アプリの応答性を損なうことなく、ユーザーに処理結果を迅速に表示することができます。

○サンプルコード7:複数の非同期タスクの連携

時として、複数の非同期タスクを連携させる必要が生じます。

例えば、1つの非同期タスクの終了をトリガーとして、別の非同期タスクを実行するケースが考えられます。

これにはDispatchGroupを利用すると効果的です。

import Foundation

let dispatchGroup = DispatchGroup()

DispatchQueue.global().async(group: dispatchGroup) {
    print("タスク1開始")
    sleep(2)
    print("タスク1完了")
}

DispatchQueue.global().async(group: dispatchGroup) {
    print("タスク2開始")
    sleep(3)
    print("タスク2完了")
}

dispatchGroup.notify(queue: .main) {
    print("両タスク完了後の処理")
}

このコードで行っているのは、2つの非同期タスクをDispatchGroupに追加し、それらのタスクが両方とも完了した後に特定の処理を実行するというものです。

dispatchGroup.notifyメソッドを使用して、両方の非同期タスクの完了を検知し、その後の処理を記述しています。

このサンプルコードを実行すると、タスク1タスク2が非同期に実行された後、最終的に両タスク完了後の処理が出力されることが確認できます。

○サンプルコード8:非同期処理のキャンセル

非同期処理の中には、条件によっては中止する必要があるものもあります。

例えば、ユーザーのアクションによって処理を中断したり、一定時間経過しても完了しない場合にキャンセルしたい場合などです。

Swiftでは、OperationQueueを使用して非同期処理をキャンセルすることができます。

import Foundation

let operationQueue = OperationQueue()

let operation = BlockOperation {
    for i in 1...10 {
        if operation.isCancelled {
            break
        }
        print("処理中: \(i)")
        sleep(1)
    }
}

operationQueue.addOperation(operation)

// 3秒後に非同期処理をキャンセル
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
    operation.cancel()
    print("非同期処理をキャンセルしました")
}

このコードでは、10回のループを持つ非同期処理を開始し、3秒後にその処理をキャンセルしています。

operation.isCancelledプロパティを使用して、非同期処理がキャンセルされたかどうかを確認し、キャンセルされた場合はループを中断します。

上記のサンプルコードを実行すると、「処理中: 1」、「処理中: 2」、「処理中: 3」と表示された後、非同期処理がキャンセルされ、「非同期処理をキャンセルしました」というメッセージが表示されます。

このようなキャンセル処理は、特に長時間の非同期処理を行う場合や、ユーザーの入力を待ち受ける場合などに非常に役立ちます。

SwiftのOperationQueueをうまく利用して、非同期処理のキャンセルを実装することができます。

○サンプルコード9:マルチスレッドでのエラーハンドリング

非同期処理中に発生するエラーの取り扱いは、マルチスレッドプログラミングにおいて非常に重要です。

エラーが発生した場合、そのエラー情報を適切にキャッチして、ユーザーに通知するか、再試行するかの適切なアクションをとる必要があります。

ここでは、非同期処理中にエラーを投げ、それをキャッチして処理する簡単な例を紹介します。

import Foundation

enum SampleError: Error {
    case unknownError
}

let operationQueue = OperationQueue()

let operation = BlockOperation {
    do {
        throw SampleError.unknownError
    } catch {
        print("エラーが発生しました: \(error)")
    }
}

operationQueue.addOperation(operation)

このコードでは、非同期処理の中でSampleError.unknownErrorというエラーをスローしています。

そして、そのエラーをcatch節でキャッチし、エラーメッセージを表示しています。

このサンプルコードを実行すると、「エラーが発生しました: unknownError」というメッセージが表示されることが確認できます。

Swiftにおける非同期処理では、エラーハンドリングをしっかりと行うことで、予期しない問題やクラッシュを防ぐことができます。

エラーが発生する可能性のある処理を書く際には、必ずエラーハンドリングを考慮に入れるようにしましょう。

○サンプルコード10:大量のデータ処理の最適化

大量のデータを処理する際、すべての処理をメインスレッドで行うとアプリケーションがフリーズしてしまう可能性があります。

そのため、非同期処理を使用してバックグラウンドで処理を行うことが推奨されます。

ここでは、大量のデータを非同期に処理するサンプルコードを紹介します。

import Foundation

let dataCount = 100000
let operationQueue = OperationQueue()

let operation = BlockOperation {
    for i in 1...dataCount {
        // 何らかのデータ処理
    }
    DispatchQueue.main.async {
        print("データ処理完了")
    }
}

operationQueue.addOperation(operation)

このコードでは、指定された回数だけデータ処理を行った後、データ処理が完了したことをメインスレッドで表示しています。

このように、大量のデータ処理は非同期で行うことで、アプリケーションの応答性を保持しつつ、効率的に処理を進めることができます。

特に、ユーザーエクスペリエンスを維持するためには、このような非同期処理の活用が非常に重要です。

●マルチスレッド実装時の注意点と対処法

Swiftでマルチスレッドの処理を実装する際、予期せぬバグや問題が生じる可能性があります。

そのような問題を未然に防ぐための注意点と、問題が生じた際の対処法をいくつかご紹介します。

○データの競合とその対処法

マルチスレッド環境での最も一般的な問題の一つはデータの競合です。

複数のスレッドが同時に同じデータにアクセスし、そのデータを変更しようとすると、データの整合性が失われる可能性があります。

このような問題を回避するための一つの方法は、排他制御を行うことです。

import Foundation

let lock = NSLock()
var sharedResource = 0

let operation1 = BlockOperation {
    lock.lock()
    for _ in 0..<100 {
        sharedResource += 1
    }
    lock.unlock()
}

let operation2 = BlockOperation {
    lock.lock()
    for _ in 0..<100 {
        sharedResource -= 1
    }
    lock.unlock()
}

let queue = OperationQueue()
queue.addOperations([operation1, operation2], waitUntilFinished: true)

print("共有リソースの値: \(sharedResource)")

このコードでは、NSLockを使用して、共有リソースへのアクセスを排他的に行っています。

そのため、複数のスレッドから同時にリソースにアクセスされることを防ぐことができます。

実行すると、共有リソースの値は必ず0と表示されます。

○UIスレッドのブロック回避方法

iOSやmacOSのアプリケーションでは、UIの更新はメインスレッドで行わなければなりません。

しかし、メインスレッドで時間のかかる処理を行ってしまうと、アプリケーションが応答しなくなる可能性があります。

そのような問題を回避するために、バックグラウンドスレッドで処理を行い、その結果をメインスレッドでUIに反映させる方法が考えられます。

import Foundation

DispatchQueue.global().async {
    // バックグラウンドで時間のかかる処理
    sleep(3)

    DispatchQueue.main.async {
        // メインスレッドでUIの更新
        print("UIを更新する処理")
    }
}

このコードでは、DispatchQueue.global().asyncを使用してバックグラウンドスレッドで処理を行っています。

そして、その処理が完了した後、DispatchQueue.main.asyncを使用してメインスレッドでUIの更新を行っています。

この方法を使うと、UIがフリーズすることなく、効率的に処理を行うことができます。

○リソースの適切な解放方法

マルチスレッド環境でリソースを使用する際は、そのリソースが不要になったときに適切に解放することが重要です。

リソースを解放しないままにしておくと、メモリリークの原因となり、アプリケーションのパフォーマンスに悪影響を及ぼす可能性があります。

import Foundation

class SampleResource {
    init() {
        print("リソースを初期化")
    }

    deinit {
        print("リソースを解放")
    }
}

let queue = DispatchQueue.global()
queue.async {
    let resource = SampleResource()
    // リソースを使用する処理
}

このコードでは、SampleResourceというクラスを定義して、そのリソースが初期化されたときと解放されたときにメッセージを表示しています。

この方法を使用すると、リソースの解放のタイミングが正しく行われているかを確認することができます。

●Swiftでのマルチスレッドカスタマイズ方法

Swiftを使用してマルチスレッドを扱う際、標準的な方法だけではなく、さまざまなカスタマイズが可能です。

効率的な処理や特定のニーズに応えるためのカスタマイズ方法を紹介します。

○プライオリティのカスタマイズ

マルチスレッドのタスクにはそれぞれ優先度を設定することができます。

この優先度によって、どのタスクを先に処理するか、どのタスクを後回しにするかを制御できます。

import Dispatch

let highPriority = DispatchQueue.global(qos: .userInteractive)
let lowPriority = DispatchQueue.global(qos: .background)

highPriority.async {
    print("高優先度のタスクを実行")
}

lowPriority.async {
    print("低優先度のタスクを実行")
}

このコードでは、DispatchQueue.global(qos:)を使用して、それぞれ異なる優先度のキューを生成しています。

userInteractiveは高優先度、backgroundは低優先度を意味します。

このようにキューの優先度をカスタマイズすることで、実行順序やリソースの割り当てを制御することができます。

○カスタムキューの作成と利用方法

デフォルトのキューだけでなく、独自のキューを作成して使用することもできます。

これにより、特定のタスク群を一元管理したり、複雑なマルチスレッドの処理を柔軟に制御することができます。

let customQueue = DispatchQueue(label: "com.example.customqueue")

customQueue.async {
    print("カスタムキューでのタスク実行")
}

このコードでは、DispatchQueue(label:)を使用して、独自のキューを作成しています。

このキューは、特定の処理を集約したり、他のキューとは異なる特性や優先度を持たせるために使用できます。

カスタムキューを利用することで、アプリケーション内のさまざまなタスクの制御がより簡単になります。

特に、複雑な非同期処理や並列処理を行う際に、タスクの管理や実行順序の制御を行いやすくなります。

まとめ

Swiftを使用したマルチスレッドの実装は、アプリケーションのパフォーマンス向上や快適なユーザー体験の提供に欠かせない要素となっています。

この記事を通じて、Swiftにおけるマルチスレッドの基本からカスタマイズ方法までの多岐にわたる内容を学ぶことができたかと思います。

特に、マルチスレッドを実装する際の注意点や対処法、さらには応用例やカスタマイズ方法など、Swift開発者として知っておくべき情報を詳細に解説しました。

これらの知識をもとに、効率的で安全なマルチスレッド処理を実現することができるでしょう。

Swift言語の進化とともに、マルチスレッドの取り扱いや最適化手法も日々進化しています。

常に最新の情報や技術動向に目を向け、より良いアプリケーション開発を目指していきましょう。