カテゴリー: TechiOS

Apple製のフレームワークCombineを触ってみた

はじめに

こんにちは、nukkyです。
今回はiOS13から追加されたCombineを触ってみたいと思います。

Combineとは

iOS13で新しく追加されたフレームワークの一つにCombineフレームワークがあります。
これは非同期の処理などを扱うためのフレームワークで、今まではサードライブラリで実装しているものが多くありましたが、今回Appleの正式なものとして登場しました。

また、Combineには大事な要素が三つあります。

  • Publishers
  • Subscribers
  • Operators

Publishers

Future

公式が用意しているPublishersで非同期で値を返すことが可能です。

  • 値を1つ発行してfinish
  • エラーを発行

のどちらかをすることができます。
値は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

Subscribersはイベントの購読者になります。
Publisherを購読するのに、sinkとassignが使えます。

sink

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

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

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つほど紹介します。

Prepend

ある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は後に追加したものから先に出力されます。

Append

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を純正のフレームワークで行えるので、しっかり学習し開発に組み込んでいきたいと思います。

おすすめ書籍

nukky

シェア
執筆者:
nukky
タグ: SwiftiOS

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前