こんにちは、nukkyです。
今回はiOS13から追加されたCombineを触ってみたいと思います。
iOS13で新しく追加されたフレームワークの一つにCombineフレームワークがあります。
これは非同期の処理などを扱うためのフレームワークで、今まではサードライブラリで実装しているものが多くありましたが、今回Appleの正式なものとして登場しました。
また、Combineには大事な要素が三つあります。
公式が用意しているPublishersで非同期で値を返すことが可能です。
のどちらかをすることができます。
値はGenericsになっていますので、最初にどの値を発行するかを設定しておきます。
エラーの型はError型でも、自分で定義したErrorプロトコルに準拠した型でもよいです。
// 数値 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を返します。
Future<String, APIError> { promise in let hasError = true //適当なフラグだと思って if hasError { //エラーの場合はこっち promise(.failure(APIError(description: "なんかエラーだって"))) } else { //成功したので値を返す promise(.success("Hello Combine!")) } }
Subscribersはイベントの購読者になります。
Publisherを購読するのに、sinkとassignが使えます。
sinkは2種類あって、エラーがある場合は以下のコード、
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)") })
エラーがない場合は以下のコードになります。
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はエラーが出ないPublisherのときに、以下のコードのように、あるオブジェクトのプロパティに直接値を代入できます。
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はPublisherプロトコル適合にし、上流のPublisherに登録して、出力された値を受け取って変換した結果を、下流のSubscriberに送ります。
簡単な例だと以下のようにPublisherに対してmapで値を加工することができます。
let publisher = Future<String, APIError> { promise in promise(.success("Hello Combine!")) } .map { $0 + " This is map operator" }
OperatorをCombining Operator(結合するOperator)と呼び、いくつかの種類があるので、2つほど紹介します。
あるPublisherの前に値が出力されるようにPublisherを挿入します。
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は後に追加したものから先に出力されます。
Prependとは反対に対象のPublisherの後に値を出力します。
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を純正のフレームワークで行えるので、しっかり学習し開発に組み込んでいきたいと思います。