34
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Swift】Combine入門レベルのまとめ

Last updated at Posted at 2022-02-01

この記事でわかること

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 再発行者
流れてきたイベントを加工する側の人間

この人達の関係性のイメージはこんな感じですかね。
スクリーンショット 2022-01-30 14.00.24.png

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を使っています。
SubjectPublisherを継承したプロトコルです。

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の種類が多いのと、サンプルアプリ作ってみないと実感わかない。
ってことで、まだまだ学習が必要ですね。

参考文献

34
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?