はじめに
こんにちは、nukkyです。
今回はiOS13から追加されたCombineを触ってみたいと思います。
Combineとは
iOS13で新しく追加されたフレームワークの一つにCombineフレームワークがあります。
これは非同期の処理などを扱うためのフレームワークで、今まではサードライブラリで実装しているものが多くありましたが、今回Appleの正式なものとして登場しました。
また、Combineには大事な要素が三つあります。
- Publishers
- Subscribers
- Operators
Publishers
Future
公式が用意しているPublishersで非同期で値を返すことが可能です。
- 値を1つ発行してfinish
- エラーを発行
のどちらかをすることができます。
値はGenericsになっていますので、最初にどの値を発行するかを設定しておきます。
エラーの型はError型でも、自分で定義したErrorプロトコルに準拠した型でもよいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 数値 Future<Int, APIError> // こんなのがあったら struct Company { var name: String } Future<Company, APIError> // エラーが複数あって使い分けたければ struct SampleError: Error { var description: String } struct APIError: Error { var description: String } Future<Company, Error> |
成功する場合は.successを返して、失敗した場合は.failureを返します。
1 2 3 4 5 6 7 8 9 10 | Future<String, APIError> { promise in let hasError = true //適当なフラグだと思って if hasError { //エラーの場合はこっち promise(.failure(APIError(description: "なんかエラーだって"))) } else { //成功したので値を返す promise(.success("Hello Combine!")) } } |
Subscribers
Subscribersはイベントの購読者になります。
Publisherを購読するのに、sinkとassignが使えます。
sink
sinkは2種類あって、エラーがある場合は以下のコード、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let pub = Future<String, Error> { promise in promise(.success("hello")) } let cancellable = pub.sink(receiveCompletion: { completion in switch completion { case .finished: break case .failure(let error): print("error \(error)") } }, receiveValue: { value in print("value \(value)") }) |
エラーがない場合は以下のコードになります。
1 2 3 4 5 6 7 8 9 | let pub = Future<String, Error> { promise in promise(.failure(APIError(description: "Mmmm. There is an error"))) }.catch { error in Just("エラー時のデフォルトメッセージ") } let cancellable = pub.sink { message in print("message \(message)") } |
assign
assignはエラーが出ないPublisherのときに、以下のコードのように、あるオブジェクトのプロパティに直接値を代入できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class MessageContainer { var message: String init() { message = "initial message" } } let pub = Future<String, Error> { promise in promise(.success("hello assign")) }.catch { error in Just("エラー時のデフォルトメッセージ") } let obj = MessageContainer() print("message before: \(obj.message)") let cancellable = pub.assign(to: \.message, on: obj) print("message after: \(obj.message)") // 出力 // message before: initial message // message after: hello assign |
Operators
OperatorsはPublisherプロトコル適合にし、上流のPublisherに登録して、出力された値を受け取って変換した結果を、下流のSubscriberに送ります。
簡単な例だと以下のようにPublisherに対してmapで値を加工することができます。
1 2 3 4 5 6 | let publisher = Future<String, APIError> { promise in promise(.success("Hello Combine!")) } .map { $0 + " This is map operator" } |
OperatorをCombining Operator(結合するOperator)と呼び、いくつかの種類があるので、2つほど紹介します。
Prepend
あるPublisherの前に値が出力されるようにPublisherを挿入します。
1 2 3 4 5 6 7 8 9 10 11 12 | var subscriptions = Set<AnyCancellable>() let publisher = ["c", "d"].publisher publisher .prepend("a", "b") .sink(receiveValue: { print($0) }) .store(in: &subscriptions) // 出力結果 a b c d |
prependは後に追加したものから先に出力されます。
Append
Prependとは反対に対象のPublisherの後に値を出力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var subscriptions = Set<AnyCancellable>() let publisherA = ["c", "d"].publisher let publisherB = ["a", "b"].publisher publisherA .append(publisherB) .sink(receiveValue: { print($0) }) .store(in: &subscriptions) // 出力結果 c d a b |
appendは前に追加したものから先に出力さます。
さいごに
今まではRXSwiftなどで行なっていたFRPを純正のフレームワークで行えるので、しっかり学習し開発に組み込んでいきたいと思います。