Swiftでアプリ開発をしていると、「ネットワーク通信中に画面が固まる」「複数の処理を同時に実行したい」といった場面に遭遇しますよね。そんなときに活躍するのがSwift Concurrencyです。
この記事では、Swift初心者の方でも理解できるように、Swift Concurrencyの基礎から実践的な使い方まで、わかりやすく解説します。
Swift Concurrencyとは?
Swift Concurrencyは、Swiftプログラミング言語に組み込まれた非同期処理と並行処理を安全に扱うための機能です。Swift 5.5(2021年リリース)で導入され、iOSやmacOSアプリ開発における非同期処理の新しい標準となっています。
なぜSwift Concurrencyが必要なのか?
アプリ開発では、以下のような処理を頻繁に扱います。
- ネットワークからのデータ取得
- データベースへの読み書き
- 画像の加工処理
- ファイルの読み込み
これらの処理を同期的に実行すると、処理が完了するまでアプリが固まってしまいます。Swift Concurrencyを使えば、これらの重い処理をバックグラウンドで実行しながら、UIはスムーズに動作させることができます。
Swift Concurrencyの5つの主要機能
1. async/await – 非同期処理を読みやすく
async/awaitは、非同期処理を同期処理のように書ける構文です。
従来の書き方(コールバック)
func fetchUserData(completion: @escaping (Result<User, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
// データ処理...
completion(.success(user))
}.resume()
}
// 使用時
fetchUserData { result in
switch result {
case .success(let user):
print(user.name)
case .failure(let error):
print(error)
}
}
Swift Concurrencyの書き方
func fetchUserData() async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url)
let user = try JSONDecoder().decode(User.self, from: data)
return user
}
// 使用時
Task {
do {
let user = try await fetchUserData()
print(user.name)
} catch {
print(error)
}
}
コードが大幅にシンプルになり、読みやすくなりました。
2. Actor – データ競合を自動的に防ぐ
Actorは、複数のスレッドから同時にアクセスされてもデータが壊れないことを保証する型です。
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) -> Bool {
guard balance >= amount else { return false }
balance -= amount
return true
}
func getBalance() -> Double {
return balance
}
}
// 使用例
let account = BankAccount()
Task {
await account.deposit(1000)
let balance = await account.getBalance()
print("残高: \(balance)円")
}
Actorを使うことで、コンパイラが自動的にデータ競合をチェックしてくれます。
3. Task – 非同期処理の実行単位
Taskは、非同期処理を実行するための基本単位です。
// 基本的なTask
Task {
let data = try await fetchData()
print(data)
}
// キャンセル可能なTask
let task = Task {
for i in 1...10 {
try Task.checkCancellation() // キャンセルチェック
print(i)
try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒待機
}
}
// 3秒後にキャンセル
Task {
try await Task.sleep(nanoseconds: 3_000_000_000)
task.cancel()
}
4. TaskGroup – 複数の処理を並行実行
TaskGroupを使うと、複数の非同期処理を並行に実行し、すべての結果を待つことができます。
func downloadImages(urls: [URL]) async throws -> [UIImage] {
try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw ImageError.invalidData
}
return image
}
}
var images: [UIImage] = []
for try await image in group {
images.append(image)
}
return images
}
}
5. MainActor – UIの更新を安全に
MainActorは、メインスレッドで実行する必要がある処理(主にUI更新)を明示的に指定します。
@MainActor
class ViewModel: ObservableObject {
@Published var userName: String = ""
func loadUser() async {
let user = try? await fetchUserData()
// MainActorなので、自動的にメインスレッドで実行される
self.userName = user?.name ?? "Unknown"
}
}
// または部分的に指定
func updateUI() async {
let data = await fetchData() // バックグラウンドで実行
await MainActor.run {
// この中だけメインスレッドで実行
label.text = data
}
}
実践例:天気情報アプリ
実際のアプリ開発を想定した例を見てみましょう。
struct WeatherResponse: Codable {
let temperature: Double
let description: String
}
actor WeatherService {
private let baseURL = "https://api.weather.com"
private var cache: [String: WeatherResponse] = [:]
func getWeather(for city: String) async throws -> WeatherResponse {
// キャッシュチェック
if let cached = cache[city] {
return cached
}
// API呼び出し
let url = URL(string: "\(baseURL)/weather?city=\(city)")!
let (data, _) = try await URLSession.shared.data(from: url)
let weather = try JSONDecoder().decode(WeatherResponse.self, from: data)
// キャッシュに保存
cache[city] = weather
return weather
}
}
@MainActor
class WeatherViewModel: ObservableObject {
@Published var temperature: String = ""
@Published var description: String = ""
@Published var isLoading: Bool = false
private let service = WeatherService()
func loadWeather(for city: String) async {
isLoading = true
defer { isLoading = false }
do {
let weather = try await service.getWeather(for: city)
self.temperature = "\(weather.temperature)°C"
self.description = weather.description
} catch {
self.description = "読み込みエラー"
}
}
}
従来の方法との比較
GCD(Grand Central Dispatch)との違い
項目 | GCD | Swift Concurrency |
---|---|---|
記述方法 | DispatchQueue、クロージャ | async/await |
可読性 | ネストが深くなりがち | 直線的で読みやすい |
エラーハンドリング | 複雑 | try/catchで統一 |
データ競合の防止 | 手動で管理 | Actorで自動保護 |
キャンセル | 手動実装が必要 | Task.cancel()で簡単 |
Combineフレームワークとの違い
Combineはリアクティブプログラミングのフレームワークで、データストリームの処理に特化しています。一方、Swift Concurrencyは一般的な非同期処理全般に使えます。
用途に応じて使い分けるのがベストですが、シンプルな非同期処理であればSwift Concurrencyの方が学習コストが低いです。
Swift Concurrency使用時の注意点
1. iOS・macOSのバージョン要件
Swift Concurrencyを使用するには以下のバージョンが必要です。
- iOS 13.0以上
- macOS 10.15以上
- Swift 5.5以上
2. async関数は別のasync関数またはTask内でしか呼べない
// ❌ エラー:通常の関数からasync関数は呼べない
func normalFunction() {
let data = await fetchData() // コンパイルエラー
}
// ✅ 正しい:Taskで囲む
func normalFunction() {
Task {
let data = await fetchData()
}
}
3. Actorのプロパティへのアクセスはawaitが必要
actor Counter {
var count = 0
}
let counter = Counter()
// ❌ エラー
print(counter.count)
// ✅ 正しい
Task {
let value = await counter.count
print(value)
}
まとめ
Swift Concurrencyは、非同期処理をシンプルかつ安全に書くための強力な機能です。主なポイントをおさらいしましょう。
- async/awaitで非同期処理を同期処理のように書ける
- Actorでデータ競合を自動的に防止できる
- Taskで非同期処理を簡単に実行・管理できる
- TaskGroupで複数の処理を並行実行できる
- MainActorでUI更新を安全に行える
最初は慣れないかもしれませんが、Swift Concurrencyを使いこなせるようになると、より安全で保守性の高いコードが書けるようになります。まずは簡単なネットワーク処理から始めて、徐々に複雑な処理にも挑戦してみてください。