この記事でわかること
Combineを利用する上で理解が必須になってくる
- Publisher
- Subscriber
- Subject
- Operator
の関係性をシンプルなサンプルコードを使って説明していきます。
Combineとは
iOS13から追加されたApple純正のFrameworkです。
Swiftのみで利用可能で、Objective-Cでは利用できないようです。
Combineの特徴を簡単に言葉にしてみると、
イベントを発行する側と受け取る側に分かれて、あるイベントが発行されたら、それを受け取った側の処理が走ることを容易にしたフレームワーク
という感じで捉えています。
公式ドキュメントを呼んでみるとこんな説明が、
By adopting Combine, you’ll make your code easier to read and maintain, by centralizing your event-processing code and eliminating troublesome techniques like nested closures and convention-based callbacks.
要するに、Swiftの特徴である、クロージャを使ったコールバックでコードの可読性が下がったり、処理が難しくなってしまうことを防ぎ、コードのメンテナンス性の向上が期待できる。
という感じです。個人的にクロージャのコールバック地獄のコードは辛いのでこれは嬉しいです。
個人的なメモ
今回、Combineのサンプルを漁っている時に感じたことは、
「これって、RxSwift
にめちゃくちゃ似てるじゃないですか。」
MVVMアーキテクチャの開発は、RxSwift
一択だと思っていましたが、純正って響きが良きです。
あと、iOS13〜ということで、SwiftUI
との相性が良さそうなのも🙆♂️
2021年にiOS15がリリースされたので、これから新規開発するアプリは、iOS13-15をサポートすればいいと考えると、そろそろSwiftUI
が本格的に導入され、それに伴ってCombineも来るのか!?って思っちゃいました。
Combineの主な登場人物
名前 | 役割 |
---|---|
Publisher | 発行者 イベントを発行する側の人間 |
Subscriber | 購読者 イベントを受け取って処理をする側の人間 |
Operator | 再発行者 流れてきたイベントを加工する側の人間 |
PublisherとSubscriberについて
Combineを利用する上で、極めてシンプルで簡単なサンプルを書いてみました。
import UIKit
import Combine
// 発行者を生成する
let publisher = ["スパイダーマン", "タイタニック", "ワイルド・スピード"].publisher
// 購読登録を行う
publisher.sink { string in
print("\(string)を鑑賞します")
}
// 出力
スパイダーマンを鑑賞します
タイタニックを鑑賞します
ワイルド・スピードを鑑賞します
ここで、AmazonPrimeやNetflixといった、各種配信サブスクを想像してください。
配列の中身は、「発行者」の持っている「コンテンツ」であり、
このコードの関係性が、「発行者」->「購読者」の関係になっていることがなんとなくわかると思います。
sinkについて
ここで、sink
というメソッドが登場しましたが、こちらについては、ドキュメントに
Attaches a subscriber with closure-based behavior to a publisher that never fails.
と書かれていますが、
クロージャ処理をするところが「Subscriber(購読者)」の振る舞いとなり、それを定義済の「Publisher(発行者)」と紐付ける。
という解釈で良いと思います。
Subjectについて
先程のコードは、Subject
を使わないで実装しましたが、次は、Subject
を使っています。
Subject
はPublisher
を継承したプロトコルです。
Subject
を使うことで、外部から値を受け取ってそれを通知することができるようになります。
とりあえず、サンプルコードを見てみることにします。
let subject = PassthroughSubject<String, Never>()
subject.send("アイアンマン")
// 購読登録
subject.sink { completion in
print("complete")
} receiveValue: { string in
print("\(string)")
}
subject.send("アベンジャーズ")
subject.send(completion: .finished)
subject.send("エターナルズ")
// 出力
アベンジャーズを鑑賞します
1番目のサンプルとは、Subscriber
が値を受け取る方法が異なるのがわかると思います。
さっきは最初から定義された値がSubscriber
に渡されましたが、今回は、外部から値を受け取っているのがわかります。
また、購読登録前、completion
を投げた後は、Subject
に対してsend()
しても、購読者が持っている振る舞いは実行されないことがわかります。
Operatorについて
これまでのサンプルコードは、Operator
をあえて登場させませんでしたので、今回はOperetorを登場させたサンプルコードです。
Operator
については、冒頭でも説明しましたが、
Publisher
によって発行された値を、Subscriber
が購読する前に、値をキャッチして変換することができます。
今回は、map(_:)
というOperator
を例にしたサンプルです。
let publisher = [1,2,3].publisher
publisher.sink { value in // valueはint型で取得される
print("アベンジャーズ\(value)を鑑賞します。")
}
//アベンジャーズ1を鑑賞します。
//アベンジャーズ2を鑑賞します。
//アベンジャーズ3を鑑賞します。
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
publisher.map { value in
// 値をString型へ変更
formatter.string(from: NSNumber(integerLiteral: value)) ?? ""
}.sink { string in
print("アベンジャーズ\(string)を鑑賞します。")
}
//アベンジャーズoneを鑑賞します。
//アベンジャーズtwoを鑑賞します。
//アベンジャーズthreeを鑑賞します。
map(_:)
はシンプルなので、ほぼ説明不要かと思いますが、このOperator
を利用することで、Subscriber
に値が渡る時は、String型へ変換されて渡されていることがわかります。
おわりに
今回は以上となります。
とりあえず、Operator
の種類が多いのと、サンプルアプリ作ってみないと実感わかない。
ってことで、まだまだ学習が必要ですね。
参考文献