Closed18
iOSDCのCombineOperatorガイド(宇佐見さん)
PassthroughSubjectとは?
- Subjectを継承するclass
- CurrentValueSubjectとは違い、直近publishされた値のバッファや初期値を持たない
- subscriberがない場合は、値をドロップ、demandがzeroの場合も。
?? demandがzeroとは?
大体RxJavaなどと同じように考えればよさそう。
Subjectなどの概念。
Combineでは
- Publisher > イベントを送信するオブジェクト
- Publisherからイベントを受信することをsubscribeと呼ぶ
(Rxと一緒)
List1はこんな感じにしてみた
- Receiverが直接subjectを参照しているのが気になったので、initの引数にしてみたSubjectで行きたかったが、エラーが出たので、
PassthroughSubject<String, Never>
にした。 - subscriptionをキャンセルするとその後イベントがこないことも確認してみた。
let subject = PassthroughSubject<String, Never>()
final class Receiver {
private var subscriptions = Set<AnyCancellable>()
init(subject: PassthroughSubject<String, Never>) {
subject.sink { value in
print("Received value:", value)
print("subscriptions:", self.subscriptions.count)
if value == "い" {
self.subscriptions.first!.cancel()
}
}
.store(in: &subscriptions)
}
}
let reciever = Receiver(subject: subject)
subject.send("あ")
subject.send("い")
subject.send("う")
リスト2について
- assignはおしゃれ
-
\.value
のようなのをSwiftUIでもよくみるがよくわかっていなかった。(KeyPath) - ViewModelの値をViewに反映させるときなどに使うと良い。
-
KeyPathについて
keypath試した
使い所はまだわかっていない
import Foundation
struct Hamster {
let name: String
var age: Int
func run() -> String {
"Fun"
}
}
var hamster = Hamster(name: "Tofu", age: 0)
print(hamster.run())
let anyKeyPath: AnyKeyPath = \Hamster.age
print(hamster[keyPath: anyKeyPath]!) // 0
print(type(of: anyKeyPath).rootType) // Hamster
print(type(of: anyKeyPath).valueType) // Int
// AnyKeyPathはReadOnly
//hamster[keyPath: anyKeyPath] = 1 // コンパイルエラー 'hamster' is immutable
let partialKeyPath: PartialKeyPath<Hamster> = \.name
print(hamster[keyPath: partialKeyPath])
let writableKeyPath: WritableKeyPath<Hamster, Int> = \.age
print(hamster.age)
hamster[keyPath: writableKeyPath] = 1
print(hamster.age) // 値が変化
List3
- mapで途中に処理を追加できる
- mapのようにあるPublisherを別のPublisherに変換するメソッドをOperatorという
List4
- ViewModelにPublisherを持たせ、それをVCでsubscribeすることで、責務の分離できる
- VCはassignを使ってlabelに値を入れるだけ。
- eraseToAnyPublisherによって型消去ができる
- これは、mapなどを通していくことで、型がどんどん入子構造になり、利用時の型指定に困る(これはSwiftUIでもあった気がする)。これをAnyPyblisherという方に変えてくれる
- publisherをプロパティに持たせるときに定番で利用する
- mapは普通の変換
- replaceNilでnilの時の値を指定して変換できる
- compactMapはnilを送信できないようにできる
List11
- tryMapでthrowするとsinkのreceiveCompletionで出力できる
追加
- subject.send(completion: .finished)で正常終了できる
List12
- retryはエラー終了に対して、再接続を指定回数分試みる
例えばretry(2)で以下のようにすると、
subject.send("1")
subject.send("2")
subject.send("3")
subject.send("x")
subject.send("4")
subject.send("5")
subject.send("x")
subject.send("6")
subject.send("7")
subject.send("x")
subject.send("8")
subject.send("x")
subject.send("9")
結果は以下になる。つまりretry回数はカウントアップされている。正常値が入るとリセットされるのではなく、2回までretryするという意味
Value: 1
Value: 2
Value: 3
Value: 4
Value: 5
Value: 6
Value: 7
Completion:
failure(__lldb_expr_124.MyError.failToConvert)
フィルタリングのoperator
import Combine
let subject = PassthroughSubject<Int, Never> ()
subject.filter{$0 % 2 == 0}.sink(receiveValue: {value in print("filter % 2 == 0: ", value)})
//subject.removeDuplicates().sink(receiveValue: {value in print("removeDuplicates: ", value)})
//subject.drop(while: {$0 % 2 == 1}).sink(receiveValue: {value in print("drop while %2 == 1", value)})
//subject.dropFirst(2).sink(receiveValue: {value in print("dropFirst 2: ",value )})
//subject.prefix(2).sink{value in print("prefix 2: ", value)}
subject.send(1)
subject.send(2)
subject.send(3)
subject.send(3)
subject.send(3)
subject.send(4)
subject.send(5)
subject.send(6)
- filter 条件に一致するものをPublish
- removeDuplicates 1度出たものは2度と出さない
以下のように3を離して出しても1度しか出ない。
subject.send(1)
subject.send(2)
subject.send(3)
subject.send(3)
subject.send(3)
subject.send(4)
subject.send(5)
subject.send(6)
subject.send(3)
subject.send(3)
subject.send(6)
結果
removeDuplicates: 1
removeDuplicates: 2
removeDuplicates: 3
removeDuplicates: 4
removeDuplicates: 5
removeDuplicates: 6
- drop while
drop(while: {$0 % 2 == 1})
- 条件に当てはまるまで無視。当てはまったら条件に当てはまらなくても出す
- dropFIrst 指定した値だけ無視
- prefix 指定した値だけ出す
- combineLatest
- 値が変化したときに2つのstreamの最新の値を表示
import Combine
let subjectX = PassthroughSubject<String, Never>()
let subjectY = PassthroughSubject<String, Never>()
final class SomeObject {
var value: String = "" {
didSet {
print("didSet value: ", value)
}
}
}
let object = SomeObject()
subjectX.combineLatest(subjectY)
.map { x, y in
x + " + " + y
}
.assign(to: \.value, on: object)
subjectX.send("1")
subjectX.send("2")
subjectY.send("a")
subjectY.send("b")
subjectX.send("3")
subjectY.send("c")
didSet value: 2 + a
didSet value: 2 + b
didSet value: 3 + b
didSet value: 3 + c
- zip
- 2つ揃ったら順番に出す
import Combine
let subjectX = PassthroughSubject<String, Never>()
let subjectY = PassthroughSubject<String, Never>()
final class SomeObject {
var value: String = "" {
didSet {
print("didSet value: ", value)
}
}
}
let object = SomeObject()
subjectX.zip(subjectY)
.map { x, y in
x + " + " + y
}
.assign(to: \.value, on: object)
subjectX.send("1")
subjectX.send("2")
subjectY.send("a")
subjectY.send("b")
subjectX.send("3")
subjectY.send("c")
subjectY.send("d")
結果
didSet value: 1 + a
didSet value: 2 + b
didSet value: 3 + c
- merge
import Combine
let subjectX = PassthroughSubject<String, Never>()
let subjectY = PassthroughSubject<String, Never>()
final class SomeObject {
var value: String = "" {
didSet {
print("didSet value: ", value)
}
}
}
let object = SomeObject()
subjectX.merge(with: subjectY)
.assign(to: \.value, on: object)
subjectX.send("1")
subjectX.send("2")
subjectY.send("a")
subjectY.send("b")
subjectX.send("3")
subjectY.send("c")
subjectY.send("d")
結果
didSet value: 1
didSet value: 2
didSet value: a
didSet value: b
didSet value: 3
didSet value: c
didSet value: d
このスクラップは2021/09/12にクローズされました