Swiftにおける非同期処理の完全ガイド10選

Swiftの非同期処理のイラストSwift
この記事は約18分で読めます。

 

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

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

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

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

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

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

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

はじめに

皆さん、Swiftを使ってアプリケーション開発を行う際、パフォーマンスを最適化するために「非同期処理」について耳にしたことがあるでしょうか。

アプリのレスポンスを高速化するためや、ユーザーエクスペリエンスを向上させるために、非同期処理は欠かせない知識となっています。

この記事を読めば、Swiftでの非同期処理の基本から、その使い方、注意点、そしてカスタマイズ方法までを学ぶことができるようになります。

特に初心者の方にも分かりやすく、10のサンプルコードを交えて徹底的に解説していきます。

●Swiftと非同期処理の基本

○非同期処理とは

非同期処理とは、主要な作業を待たずに別の作業を進行させる方法のことを指します。

例えば、データのダウンロードやファイルの読み込みなど、時間のかかる処理をバックグラウンドで行いつつ、前面ではユーザーとのインタラクションをスムーズに行うことができます。

これにより、アプリケーションの応答性が向上し、ユーザー体験が大幅に良くなります。

○Swiftでの非同期処理の重要性

Swiftでのアプリ開発では、非同期処理は避けて通れないテーマとなっています。

特にiOSやmacOSのアプリ開発において、UI(ユーザーインターフェース)の更新はメインスレッドで行われるため、時間がかかる処理をメインスレッドで行ってしまうとアプリが固まってしまいます。

そのため、時間のかかる処理は別のスレッドで実行し、その結果だけをメインスレッドに反映させる、という手法が求められます。

これを実現するための技術やツールがSwiftには用意されており、その使い方や活用方法を理解し、日常の開発に取り入れることで、より良質なアプリケーションを作成することができます。

●非同期処理の使い方

Swiftにおける非同期処理の基本的な使い方を学ぶため、具体的なサンプルコードとともに解説していきます。

○サンプルコード1:DispatchQueueを使用した基本的な非同期処理

Swiftでの非同期処理の一般的な方法として、DispatchQueueを使用したものがあります。

DispatchQueueを利用することで、特定のタスクを非同期で実行することができます。

import Foundation

// 非同期で実行するタスク
DispatchQueue.global().async {
    // 長い処理を模倣
    sleep(2)
    print("非同期処理完了!")
}

print("非同期処理を開始します。")

このコードでは、非同期で2秒待機するタスクをDispatchQueue.global().asyncを使って実行しています。

非同期処理が始まった直後に”非同期処理を開始します。”が表示され、2秒後に”非同期処理完了!”が表示されます。

○サンプルコード2:非同期処理でのデータ取得

Webサービスやデータベースからのデータ取得は時間がかかるため、非同期処理で行うのが一般的です。

ここでは、URLからデータを非同期で取得する例を紹介します。

import Foundation

let url = URL(string: "https://example.com/data.json")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error {
        print("エラー: \(error)")
        return
    }

    if let data = data {
        print("データ取得成功: \(data)")
    }
}

task.resume()

このコードでは、URLSessionを使用してURLから非同期でデータを取得しています。

データの取得に成功すると”データ取得成功”というメッセージとともに取得したデータが表示され、エラーが発生した場合にはエラーメッセージが表示されます。

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

Swiftでの非同期処理の中でも、UIの更新は非常に重要なトピックです。

メインスレッドではない別のスレッドで非同期に処理を行った後、その結果をメインスレッドでUIに反映させる必要があります。

Swiftでは、DispatchQueue.mainを使ってメインスレッドにアクセスし、UIの更新を行います。

ここでは、非同期でデータを取得し、そのデータをUILabelに表示する例を紹介します。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var dataLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        DispatchQueue.global().async {
            // ここはバックグラウンドスレッドで実行される
            // 例として、3秒待機を行う
            sleep(3)
            let data = "非同期で取得したデータ"

            DispatchQueue.main.async {
                // ここはメインスレッドで実行される
                self.dataLabel.text = data
            }
        }
    }
}

このコードでは、DispatchQueue.global().asyncを用いて非同期のバックグラウンド処理を行い、3秒後にデータを取得しています。

取得したデータは、DispatchQueue.main.asyncを使ってメインスレッドでUILabelにセットしています。

このようにして、非同期での処理結果をUIに反映させることができます。

○サンプルコード4:非同期処理とエラーハンドリング

非同期処理中にはさまざまなエラーが発生する可能性があります。

例えば、ネットワーク接続のエラー、データの解析エラーなどです。これらのエラーを適切にハンドリングすることが非常に重要です。

ここでは、非同期でデータを取得し、エラーハンドリングを行う例を紹介します。

import Foundation

let url = URL(string: "https://example.com/data.json")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error {
        DispatchQueue.main.async {
            print("エラー発生: \(error.localizedDescription)")
        }
        return
    }

    guard let data = data else {
        DispatchQueue.main.async {
            print("データが存在しません。")
        }
        return
    }

    do {
        let json = try JSONSerialization.jsonObject(with: data, options: [])
        DispatchQueue.main.async {
            print("JSONデータ取得成功: \(json)")
        }
    } catch {
        DispatchQueue.main.async {
            print("JSONの解析エラー: \(error.localizedDescription)")
        }
    }
}

task.resume()

このコードでは、非同期でURLからデータを取得し、取得したデータをJSONとして解析しています。

エラーハンドリングは、URLセッションのエラーや、JSONの解析エラーを適切にキャッチし、メインスレッドでエラーメッセージを出力しています。

非同期処理とエラーハンドリングの組み合わせにより、ユーザーエクスペリエンスを向上させることができます。

●Swiftでの非同期処理の応用例

Swiftの非同期処理は、基本的な使い方だけでなく、さまざまな応用的なシチュエーションにも対応することができます。

ここでは、非同期処理の応用的な使い方として、並列実行やチェーン処理について詳しく説明していきます。

○サンプルコード5:非同期処理での並列実行

非同期処理を使って、複数のタスクを並列に実行することも可能です。

これにはDispatchQueue.concurrentPerformを使用します。

import Dispatch

let iterations = 5

DispatchQueue.concurrentPerform(iterations: iterations) { index in
    print("非同期処理 \(index + 1) 完了")
}

このコードでは、5回の非同期処理を並列に実行しています。

各非同期処理が完了すると、print文で結果を出力します。実行すると、非同期処理が複数回、ほぼ同時に完了していることを確認できます。

○サンプルコード6:非同期処理を使ったチェーン処理

非同期処理の結果を次の非同期処理の入力として使用する場合、チェーン処理を行うことができます。

DispatchSemaphoreを使用すると、非同期処理の完了を待つことができます。

import Dispatch

let semaphore = DispatchSemaphore(value: 0)

DispatchQueue.global().async {
    print("非同期処理1開始")
    sleep(2)
    print("非同期処理1完了")
    semaphore.signal()
}

semaphore.wait()

DispatchQueue.global().async {
    print("非同期処理2開始")
    sleep(2)
    print("非同期処理2完了")
}

このコードは、非同期処理1が完了した後に非同期処理2が開始されることを表しています。

非同期処理1が完了すると、semaphore.signal()が呼ばれ、次のsemaphore.wait()が解放され、非同期処理2が開始されます。

○サンプルコード7:非同期処理の遅延実行

Swiftには、特定の時間が経過した後に非同期処理を実行するための機能が実装されています。

これにより、一定の待機時間の後にタスクを開始することが可能となります。

具体的には、DispatchQueueasyncAfterメソッドを使用します。

import Dispatch

let delayTime = DispatchTime.now() + 3.0
DispatchQueue.global().asyncAfter(deadline: delayTime) {
    print("3秒後に非同期で実行されました")
}

このコードでは、現在の時間から3秒後に非同期で指定した処理が実行されます。

実行すると、3秒後に”3秒後に非同期で実行されました”というメッセージが表示されることを確認できます。

遅延実行は、ユーザーエクスペリエンスの向上やリソースの効率的な利用に役立つことが多いです。

例えば、ユーザーの操作に対するレスポンスを一時的に遅らせることで、他のタスクの優先度を上げたり、一連の操作をグループ化して実行する際に有用です。

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

非同期処理を開始した後に、特定の条件下でその処理をキャンセルする必要が生じる場合があります。

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

import Dispatch

let workItem = DispatchWorkItem {
    for i in 1...5 {
        if workItem.isCancelled {
            break
        }
        print("非同期処理 \(i) 実行中")
        sleep(1)
    }
}

DispatchQueue.global().async(execute: workItem)

DispatchQueue.global().async {
    sleep(3)
    workItem.cancel()
}

上記のコードでは、5回のループを持つ非同期処理を開始していますが、3秒後にその処理をキャンセルするように設定しています。

このため、実際には5回すべてが完了する前に非同期処理が中断されます。

非同期処理のキャンセルは、不要な処理を避けるためや、リソースの節約、エラーの対応などの目的で利用されます。

DispatchWorkItemisCancelledプロパティをチェックすることで、現在の非同期処理がキャンセルされたかどうかを判断することができます。

○サンプルコード9:非同期処理と進行状況の確認

Swiftの非同期処理を実装する際、実行中のタスクの進行状況を知ることは非常に重要です。

特に、時間のかかる非同期処理や、ユーザーに進行状況を視覚的に示す必要がある場合には、この確認が不可欠となります。

進行状況を管理するために、SwiftではProgressクラスを実装しています。

このクラスを利用することで、進行状況の更新や取得を行うことができます。

import Foundation

let totalUnitCount = Int64(5)
let progress = Progress(totalUnitCount: totalUnitCount)

DispatchQueue.global().async {
    for i in 1...5 {
        sleep(1)
        progress.completedUnitCount = Int64(i)
        print("現在の進行状況: \(progress.fractionCompleted * 100)%")
    }
}

上記のコードでは、Progressクラスを使用して5ステップの非同期処理の進行状況を管理しています。

各ステップが完了するたびに、completedUnitCountを更新することで、進行状況を表示します。

このコードを実行すると、1秒ごとに進行状況が20%ずつ増えていく様子を確認できます。

○サンプルコード10:非同期処理のカスタムディスパッチキューの作成

非同期処理の制御や管理のために、カスタムディスパッチキューの作成が可能です。

これにより、特定の非同期処理を特定のキューで実行するように管理することができ、より柔軟な非同期処理の実装が可能となります。

import Dispatch

let customQueue = DispatchQueue(label: "com.example.customQueue")
customQueue.async {
    for i in 1...5 {
        print("カスタムキューで実行中: \(i)")
        sleep(1)
    }
}

上記のコードでは、DispatchQueueクラスのlabel引数を使用して、カスタムディスパッチキューを作成しています。

その後、このカスタムキューを使って非同期処理を実行しています。

このコードを実行すると、「カスタムキューで実行中」というメッセージが5回表示されることを確認できます。

カスタムディスパッチキューの利点は、キューごとに処理の優先度を設定したり、特定のリソースへのアクセスをシリアル化して競合を防ぐなど、細かい制御が可能となる点にあります。

●非同期処理の注意点と対処法

Swiftにおける非同期処理は、アプリケーションのパフォーマンス向上やレスポンスの向上をもたらしますが、その利便性と引き換えに、開発者が気を付けるべき点がいくつか存在します。

ここでは、非同期処理でよく遭遇する問題やそれらの対処法について詳しく説明します。

○デッドロックとその回避策

デッドロックは、2つ以上のタスクが互いの終了を待ち合うことによって、どちらも終了できない状態を指します。

Swiftでの非同期処理において、特にメインスレッドとバックグラウンドスレッドの間でこのような問題が起こりやすいです。

例として、メインスレッドで非同期処理を開始し、その非同期処理が終わるのをメインスレッドで待ってしまう場合、デッドロックが発生します。

// デッドロックの発生例
let queue = DispatchQueue.global()
queue.sync {
    DispatchQueue.main.sync {
        print("これは実行されません")
    }
}

このコードを実行すると、グローバルキューでDispatchQueue.main.syncを呼び出しているため、メインスレッドが現在のタスクの完了を待っている間、新しいタスクがメインスレッドに投げられることはありません。

このようなデッドロックを避けるための対処法として、非同期処理の実行には常にasyncを使用し、特にsyncを使う場合は、デッドロックが発生しないか注意深くコードを確認することが求められます。

○非同期処理のテスト方法

非同期処理のテストは、同期処理とは異なり、特別な考慮が必要です。

非同期処理の完了を待つことなくテストが終了してしまう場合や、非同期処理による結果の検証が困難な場合が考えられます。

Swiftでは、XCTestフレームワークを使用して非同期処理のテストをサポートしています。

import XCTest

class AsyncTest: XCTestCase {
    func testExample() {
        let expectation = self.expectation(description: "非同期処理の完了")

        DispatchQueue.global().async {
            sleep(2)
            expectation.fulfill()
        }

        waitForExpectations(timeout: 5) { error in
            if let error = error {
                print("エラー: \(error.localizedDescription)")
            }
        }
    }
}

このコードでは、非同期処理の完了を監視するためのexpectationを作成しています。

非同期処理が完了すると、expectation.fulfill()を呼び出し、waitForExpectationsメソッドを使用して、非同期処理が指定したタイムアウト内に完了するかをチェックします。

Swiftでの非同期処理のテストは、このようにXCTestの機能を活用することで、確実かつ効率的に行うことができます。

●非同期処理のカスタマイズ方法

Swiftにおいて非同期処理は多くの場面で利用されるものの、デフォルトの設定だけでは、要件に応じた最適な動作を実現することが難しい場合もあります。

そこで、非同期処理をより柔軟に、そして効率的に扱うためのカスタマイズ方法を2つのテーマに分けて説明していきます。

○カスタムディスパッチキューの利用

DispatchQueueは、非同期処理のためのキューとしてよく利用されますが、デフォルトのキューだけでなく、カスタムディスパッチキューも作成することができます。

カスタムディスパッチキューを利用することで、タスクの優先度や実行順序などを細かく制御することができます。

// カスタムディスパッチキューの作成と利用
let customQueue = DispatchQueue(label: "com.example.myCustomQueue", attributes: .concurrent)

customQueue.async {
    // 非同期処理の内容
    print("カスタムキューでの非同期処理")
}

このコードでは、labelでキューの識別子を指定し、attributesでキューの属性を指定しています。

この例では、.concurrentを指定して並行キューを作成しています。

このカスタムディスパッチキューを利用することで、特定のタスクグループに対して独自のキューを割り当てることができ、処理の見通しが良くなります。

○非同期処理の優先度管理

Swiftの非同期処理では、タスクの優先度を管理することができます。

これにより、重要なタスクを先に実行したり、背景での低優先度タスクを遅らせることができます。

// 優先度を指定して非同期処理を実行
DispatchQueue.global(qos: .userInitiated).async {
    // 高優先度の非同期処理
    print("高優先度のタスク")
}

DispatchQueue.global(qos: .background).async {
    // 低優先度の非同期処理
    print("背景での低優先度タスク")
}

このコードでは、qosパラメータを利用して、非同期処理の優先度を指定しています。

.userInitiatedはユーザーのアクションに応じて即座に実行すべきタスク、.backgroundはユーザーからの直接的な操作に関連しない背景でのタスクに適しています。

これらの優先度を適切に設定することで、アプリケーションの応答性やリソースの有効活用が向上します。

まとめ

Swiftにおける非同期処理は、多くのアプリケーション開発で欠かせない技術となっています。

この記事を通して、基本的な非同期処理の概念から、その実装方法、さらにはカスタマイズや応用例について詳細に解説しました。

初心者の方から経験者まで、非同期処理の幅広い知識と実践的な技術を身につけることができる内容となっています。

特にカスタムディスパッチキューの利用や非同期処理の優先度管理など、実際の開発現場で役立つテクニックを多数紹介しています。

Swiftでのアプリケーション開発において、効率的かつ高品質な非同期処理を実現するための情報を網羅していますので、今後の開発に活かしていただければ幸いです。

非同期処理を上手く活用することで、より応答性が高く、ユーザーフレンドリーなアプリケーションを作成することが可能となります。