Contents
SwiftUIとCombineの連携 方法を実践で学ぶ入門ガイド
SwiftUIでのデータバインディングや非同期処理を構築する際、Combineフレームワークは欠かせない存在です。特に2023年現在では、@Observableマクロの導入によりコードがさらに簡潔になりつつありますが、Combineによるリアクティブな設計思想は依然として中心的な技術です。本記事では、CombineのPublisher/Subscriberモデルから具体的な実装例までを解説し、SwiftUIとCombineの連携方法を網羅的に理解していただきます。
SwiftUIとCombineの連携における基本設計思想
リアクティブプログラミングは、ユーザーインターフェースや非同期処理の設計において重要な考え方です。以下では、Combineフレームワークの原理とSwiftUIとの連携方法について詳しく解説します。
リアクティブプログラミングの概要
リアクティブプログラミングは「データストリームを扱う方法」であり、Combineフレームワークの核になります。従来の同期処理(イベント発生→即時処理)と異なり、データが変化するたびに自動的に更新される非同期処理を実現します。
- 例: ユーザー入力 → キーボード入力の変化を検知し、リアルタイムでフィルタリング
- メリット: 非同期処理の複雑さを抽象化し、コードの可読性と保守性を向上
Publisher/Subscriberモデルの役割
Combineでは「Publisher(発信者)」がデータを流し、「Subscriber(購読者)」がそのデータを受け取るモデルを採用しています。
| 項目 | 説明 |
|---|---|
| Publisher | データの発信元(例: @Publishedプロパティ、API通信結果) |
| Subscriber | データの受け取り先(例: SwiftUI View、ViewModel) |
このモデルにより、データ変化を明示的に登録・解約できるため、メモリリークのリスクを抑えることができます。
CombineによるSwiftUIデータバインディングの仕組み
SwiftUIのViewは、@Publishedプロパティを介してViewModelの変更を自動で反映させます。この機構にはCombineが裏側で働いており、以下のように動作します。
@Publishedプロパティの動作原理
@Publishedプロパティは、値が変更されたときに自動的にSubscriberに通知するPublisherとして機能します。
|
1 2 3 4 |
class TodoViewModel: ObservableObject { @Published var todos: [String] = [] } |
上記コードではtodos配列の変化を、SwiftUIのViewが自動で検知し、UIを再描画します。
View内のサブスクリプション管理
SwiftUIはViewライフサイクルに合わせてサブスクリプションを自動管理していますが、手動でのサブジェクト終了処理が必要なケースもあります。
blockquote
注意: CombineのAnyCancellableを保持していない場合、サブスクリプションがリークする可能性があります。
実践的なCombine演算子活用法
Combineの「Operator(演算子)」はデータ変換や処理フローの制御に不可欠です。代表的なfilter/mapやdebounceなどの使用例を解説します。
filter/mapによるデータ変換フロー
ユーザー入力に対してリアルタイムでフィルタリングを行う際、filterとmapを組み合わせて処理することができます。
|
1 2 3 4 5 6 7 8 |
let searchQuery = PassthroughSubject<String, Never>() searchQuery .debounce(for: 0.5, scheduler: DispatchQueue.main) .filter { $0.count >= 3 } .map { filteredTodos($0) } .sink(receiveValue: { self.filtered = $0 }) .store(in: &cancellables) |
debounce: 一定時間入力がない状態で処理を実行(検索ボックスの最適化に)filter: 文字数3文字以上のときのみ処理を実行
debounce/sinkの最適化例
API通信などの非同期処理では、複数回のリクエストを抑制するためにdebounceやthrottleが有効です。
|
1 2 3 4 5 6 7 |
let networkCall = PassthroughSubject<Void, Never>() networkCall .debounce(for: 2.0, scheduler: DispatchQueue.main) .flatMapLatest { fetchUser() } .sink(receiveValue: { self.user = $0 }) .store(in: &cancellables) |
ViewModelとViewの通信フロー構築
CombineではViewModelがビジネスロジックを担当し、ViewはUI更新に特化するMVVMアーキテクチャが推奨されます。
@StateObjectの適切な使い方
SwiftUIでViewModelを初期化する際は@StateObjectを使用し、Viewのライフサイクルと連動させます。
|
1 2 3 4 5 |
struct ContentView: View { @StateObject private var viewModel = TodoViewModel() // ... } |
@StateObjectの特徴:
- View初期化時に自動でViewModelを生成
- Viewが破棄されると自動的に解放される(メモリリーク防止)
データ流の分離と再利用
ViewModelは以下の役割を持ちます。
| 項目 | 説明 |
|---|---|
| データ取得 | API通信やローカルストレージからの読み込みを担当 |
| 処理実行 | フィルタリング、計算などビジネスロジックの実装 |
| 通知 | @PublishedプロパティでViewに状態変化を通知 |
@Observableマクロとの連携方法
SwiftUI 5.9以降では、新しい@Observableプロパティラッパーが導入され、Combineと併用するケースが増えています。
新旧プロパティラッパーの比較
| 項目 | @Published | @Observable |
|---|---|---|
| 使用目的 | Combineとの連携 | SwiftUI専用の簡易バインディング |
| コード量 | ViewModelが必要 | View内に直接定義可能 |
| 非同期処理 | Combineと組み合わせて使用 | 限定的な用途(シンプルなデータ変更) |
@ObservableとCombineの連携方法
@Observableは、Combineとは独立して動作するプロパティラッパーです。非同期処理が必要な場合は以下のように併用します。
|
1 2 3 4 5 6 7 8 9 |
@Observable class UserViewModel { var name: String = "" func fetchName() async { let newName = await API.getName() self.name = newName } } |
blockquote
注意:@Publishedと異なり、@ObservableはCombineのPublisher/Subscriberモデルをサポートしていません。非同期処理が必要な場合は、CombineのFutureやAnyCancellableを併用する必要があります。
サンプルコードで学ぶリアクティブUI設計
Todoアプリの実装例
以下はTodoアイテムを管理するシンプルなアプリです。Combineによるリアルタイム更新処理が体験できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import SwiftUI import Combine class TodoViewModel: ObservableObject { @Published var todos: [String] = [] func addTodo(_ text: String) { todos.append(text) } } struct ContentView: View { @StateObject private var viewModel = TodoViewModel() var body: some View { VStack { TextField("新しいタスク", text: $viewModel.todos.isEmpty ? "" : $viewModel.todos[0]) .onSubmit { viewModel.addTodo($0) } List(viewModel.todos, id: \.self) { item in Text(item) } } } } |
リアルタイム更新処理
上記コードでは@Publishedプロパティが変更されると、Listビューが自動で再描画されます。Combineのリアクティブな特徴を体感してください。
- SwiftUIとCombineの連携は、データバインディングや非同期処理において不可欠な技術です
@Publishedは依然として基本的な設計思想ですが、@Observableも併用できる選択肢- 実際に動作するコードを試して、リアクティブなUIの良さを感じてください
CTA: 記事に掲載したサンプルコードを実際に動作させて、CombineによるリアクティブなUI設計の良さを体験してみてください