TL; DR
- 3年前に流行ったツイートとRxSwiftを使った記事を思い出したのでCombineで再現してみました
ズンドコキヨシをCombineで再現してしまった...
— mshrwtnb (@mshrwtnb_) October 25, 2019
- 元ネタはこちら
Javaの講義、試験が「自作関数を作り記述しなさい」って問題だったから
— てくも (@kumiromilk) March 9, 2016
「ズン」「ドコ」のいずれかをランダムで出力し続けて「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら「キ・ヨ・シ!」って出力した後終了って関数作ったら満点で単位貰ってた
0. ゴールの確認
- 「ずん」「どこ」「き・よ・し!」の出力はすべてreceiveValueで受け取る
String
とする(= assignしようと思えばできる状態を目指す)。記事ではinput:○○○
の○○○
で表現する。 - Publisherの
print(_:to:)
はデバグ用に使う(回答としてカウントしない) - 一度「き・よ・し!」を出力したら処理を終了する
1. ずんどこ川の水源を作る
まずは「ずん」または「どこ」が湧き出る水源を作っていく。
勢いよく湧き出て欲しいので、0.1秒間隔で出力していこう。
iOS 13から追加されたTimer.publish(every:tolerance:on:in:options:)
を使っていく。
let ずんどこ水源 =
Timer.publish(every: 0.1, on: .main, in: .default)
.autoconnect()
.map { _ in Int.random(in:0..<2) == 0 ? "ずん" : "どこ" }
.share()
self.cancellable =
ずんどこ水源
.sink(receiveCompletion: { _ in
print("===完===")
}, receiveValue: {
print("input: \($0)")
})
良い。すごく良い。勢いある湧出量だ。
2. センサー付きの水門づくり
上述のとおり、ずんどこ水源からは「ずん」または「どこ」が1語ずつ流れてくる。
検知したいのは「ずん、ずん、ずん、ずん、どこ」の5要素なので、FIFO方式でずんどこが5つだけたまる溜池を作る。
そして、溜まった要素がお目当てのパターンか判定するセンサーを作る。
もしパターンを検知できたら水門を閉じる。
いたずらにずんどこを溜めない。
let センサー付き水門 =
ずんどこ水源
.scan([], { (reservoir: [String], element: String) -> [String] in
// 溜池
var newReservoir = Array(reservoir + [element])
if newReservoir.count > 5 {
newReservoir.removeFirst()
}
return newReservoir.prefix(5).map { $0 }
})
.allSatisfy { $0 != ["ずん", "ずん", "ずん", "ずん", "どこ"] }
.map { _ in }
.share()
3. 検知後「き・よ・し!」を知らせる
()
が流れてくる仕組みを活用し、「き・よ・し!」と流れるスピーカーを作ろう。
let スピーカー = センサー付き水門.map { _ in "き・よ・し!" }
これでも悪くはない。
しかし、贅沢を言えば「どこ」と「き・よ・し!」の間には"ため"が欲しい。
そこで1秒くらい"ため"を作ろう。
結果には影響を与えない形で"ため"を確認できるようprintしておこう。
let スピーカー = センサー付き水門.map { _ in "き・よ・し!" }
.handleEvents(receiveOutput: { _ in
print("(せーの...!)")
})
.delay(for: .seconds(1.0), scheduler: RunLoop.main)
4. 整備されたずんどこ川の全貌
野川状態だった、ずんどこ川にも必要な設備が揃った。
あとはこれらをうまく組み合わせていく。
これで、河川管理もバッチリだ。
let ずんどこ水源 =
Timer.publish(every: 0.1, on: .main, in: .default)
.autoconnect()
.map { _ in Int.random(in:0..<2) == 0 ? "ずん" : "どこ" }
.share()
let センサー付き水門 =
ずんどこ水源
.scan([], { (reservoir: [String], element: String) -> [String] in
// 溜池
var newReservoir = Array(reservoir + [element])
if newReservoir.count > 5 {
newReservoir.removeFirst()
}
return newReservoir.prefix(5).map { $0 }
})
.allSatisfy { $0 != ["ずん", "ずん", "ずん", "ずん", "どこ"] }
.map { _ in }
.share()
let スピーカー = センサー付き水門.map { _ in "き・よ・し!" }
.handleEvents(receiveOutput: { _ in
print("(せーの...!)")
})
.delay(for: .seconds(1.0), scheduler: RunLoop.main)
self.cancellable =
ずんどこ水源
.prefix(untilOutputFrom: センサー付き水門) // センサーが検知するまで、ずんどこを流す
.merge(with: スピーカー) // 検知したら、き・よ・し!を流す
.sink(receiveCompletion: { _ in
print("===完===")
}, receiveValue: {
print("input: \($0)")
})
よし、最後にこれを動かしてみよう。
...
....
.....
💪💪💪💪💪💪💪💪💪💪💪💪💪💪💪
あとがき
ふざけているようですが、トライしてみるとオペレーターの挙動に詳しくなります
もっと良い実装例があったら@mshrwtnbまで教えていただけると勉強になります
https://github.com/mshrwtnb/zundoko-combine にソースを置きました。
以上