はじめに
Combineは、iOS 13以降で利用可能なApple純正のリアクティブプログラミングフレームワークです。
非同期処理やデータフローの操作を簡潔かつ効率的に行うことができ、コードの可読性と保守性を向上させる強力なツールです。
Combineの主要コンセプト
3つの主要な概念を理解する必要があります。
・Publisher
・Subscriber
・Operator
あとはimportが必要(これが無いとビルドエラーが発生するよ)
import Combine
Publisher
Publisherは、時間の経過とともに値を生成し、イベントを発行するオブジェクトです。
let publisher = [“1”, “2”, “3”, “4”, “5”].publisher
Subscriber
Subscriberは、Publisherが発行したイベントを受け取り、処理を行うオブジェクトです。
例1:PassthroughSubjectとAnyCancellableを使用した場合の例)
var cancellable: AnyCancellable?
let subject = PassthroughSubject<String, Never>() // ①
cancellable = subject.sink { num in // ②
print(num)
}
subject.send("123") // ③
subject.send("22345")
cancellable?.cancel() // ④
subject.send("cancelされているはず")
例2:PassthroughSubjectを使って、Subscriberで受信した値と完了通知を受け取る場合の例)
// PassthroughSubjectの作成
let subject2 = PassthroughSubject<Int, Never>()
// Subscriberの作成と購読
let subscription = subject2.sink(
receiveCompletion: { completion in
print("完了: \(completion)")
},
receiveValue: { value in
print("受信した値: \(value)")
}
)
// 値を発行
subject2.send(10)
subject2.send(20)
// 完了通知
subject2.send(completion: .finished)
// 完了後に値を送信しても無視される
subject2.send(30)
Operator
Operatorは、Publisherが発行したイベントを変換、フィルタリング、または結合するための関数です。例えば:
let transformedPublisher = [1, 2, 3].publisher
.map { $0 * 2 }
Combineの利点
-
非同期処理の簡素化: クロージャの入れ子やコールバック地獄を回避し、コードをより線形で理解しやすいものにします。
-
宣言的プログラミング:
データの流れを宣言的に記述できるため、コードの意図がより明確になります。 -
エラーハンドリングの統一:
エラー処理を一貫した方法で行うことができます。 -
キャンセル可能な操作:
長時間実行される処理を簡単にキャンセルできます。
実践的な使用例
UIKitと組み合わせて使用する例を示します:
class ViewModel {
@Published var searchText = ""
var cancellables = Set<AnyCancellable>()
init() {
$searchText
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.sink { [weak self] text in
self?.performSearch(with: text)
}
.store(in: &cancellables)
}
func performSearch(with text: String) {
// 検索ロジックをここに実装
}
}
この例では、@Published
プロパティラッパーを使用してPublisherを作成し、検索テキストの変更を監視しています。debounce
とremoveDuplicates
オペレータを使用して、不要な検索リクエストを減らしています。
CombineフレームワークにおけるPublisherとSubscriberの違い
CombineフレームワークにおけるPublisherとSubscriberは、非同期データストリームを扱う上で重要な2つの概念です。それぞれの役割と違いは以下の通りです:
Publisher
Publisherは、時間の経過とともに値を生成し、イベントを発行するオブジェクトです。
- データやイベントの発行元として機能します。
- 特定の型の値を時間とともに連続的に送信します。
- 例えば、外部APIからのデータフェッチやユーザーインタラクションなどの非同期イベントがPublisherとして扱われます。
Subscriber
Subscriberは、Publisherが発行したデータやイベントを受信し、処理を行うオブジェクトです]。
- Publisherが送信するデータストリームを受け取ります。
- 受信したデータに対して特定の処理を実行します。
- UIの更新やデータベースの保存などのタスクを処理します。
主な違い
-
役割:
Publisherはデータやイベントのソースとしてデータを発行し、
Subscriberはそのデータを受信して処理します]。 -
データの流れ:
Publisherからデータが発行され、Subscriberへと流れていきます。
この一方向のデータフローがCombineの特徴です。 -
型の一致:
PublisherのOutput型とSubscriberのInput型、およびそれぞれのFailure型が一致する必要があります。 -
制御:
Subscriberは受信するデータの量を制御できます。
例えば、Subscribers.Demand
を使用して、受信するデータ量を指定できます。 -
ライフサイクル:
Publisherはデータの発行を開始し、Subscriberはそのデータの受信と処理を行い、最終的に完了通知を受け取ります]。
これらの違いにより、PublisherとSubscriberは協調して動作し、非同期データストリームの効率的な処理を可能にします。
注意点
Combineの使用は慎重に検討する必要があります。
適切に使用すれば非同期処理を効率化し、コードの品質を向上させる一方で、過度な使用は可読性を損なう可能性があります。
ただし、一方で元々のCompletionハンドラーなど非同期処理全般に言えることだが、プロジェクト全体の統一感を損ない適当に個人プレーで書いたり、多用し過ぎるとコードの可読性を大きく下げることにもつながるため、注意が必要である。
Combineの利点:
- 非同期処理やデータのストリーム管理を効率化する
- エラーハンドリングを統一的に扱える
- SwiftやXcodeとの親和性が高い
一方で、可読性に関する懸念点:
- リアクティブプログラミングの概念に慣れていない開発者にとっては理解が難しい場合がある
- 複雑なデータフローを作成すると、コードの追跡が困難になる可能性がある
可読性を維持しながらCombineを活用するためのアプローチ:
- 適切な命名: クラス、変数、関数名を明確かつ意図が伝わるように命名する
- 単一責任の原則: 各PublisherやSubscriberが単一の責任を持つようにする
- コードの構造化: 複雑なデータフローを小さな単位に分割し、構造化する
- コメントの活用: 複雑な処理には適切なコメントを付ける
- チーム内での知識共有: Combineの使用方針やベストプラクティスをチーム内で共有する
結論として、Combineは強力なツールですが、プロジェクトの要件や開発チームのスキルレベルを考慮して適切に使用すべきです。可読性とメンテナンス性を保ちながら、Combineの利点を活かすバランスを取ることが重要です。
以上