私の主な問題は、サブスクライバーに変更が通知されるまで@Published
プロパティがプロパティの値を更新しない(文書化されていない)事実を回避しようとしていることです。私はそれをうまく回避することができないようです。
Subject
および@Published
プロパティの以下の工夫された組み合わせを検討してください。最初に、単純なクラス:
class StringPager {
@Published var page = 1
@Published var string = ""
}
let pager = StringPager()
そして簡単なパススルーの件名:
let stringSubject = PassthroughSubject<String, Never>()
デバッグのために、文字列プロパティをサブスクライブして印刷してみましょう。
pager.$string.sink { print($0) }
ここまでは順調ですね。次に、件名をサブスクライブし、その値に基づいてページャーを変更します。
stringSubject.sink { string in
if pager.page == 1 {
pager.string = string
} else {
pager.string = string.uppercased()
}
}
うまくいけば、このロジックにより、最初のページにいないときはいつでも、ページャー文字列を大文字にすることができます。
ここで、ページが更新されたときにstringSubjectを通じて値を送信しましょう:
pager.$page.sink {
$0 == 1 ? stringSubject.send("lowercase") : stringSubject.send("uppercase")
}
このロジックが正しい場合、小文字は常に小文字になりますが、大文字は常に大文字になります。残念ながら、それはまったく起こりません。次に出力例を示します。
pager.page = 1 // lowercase
pager.page = 2 // uppercase
pager.page = 3 // UPPERCASE
pager.page = 4 // UPPERCASE
pager.page = 1 // LOWERCASE
pager.page = 1 // lowercase
この理由は、サブジェクトをサブスクライブするときにpager.page
...の値をチェックしますが、pager.page
の更新がサブジェクトのクロージャをトリガーするため、pager.page
にはありませんまだ更新された値なので、サブジェクトは間違ったブランチを実行します。
シンクする前に件名をZip
ingしてpager.$page
を修正してみました:
stringSubject.Zip(pager.$page).eraseToAnyPublisher().sink { ...same code... }
同様にcombineLatest
ing:
stringSubject.combineLatest(pager.$page).eraseToAnyPublisher().sink { ...same code... }
しかし、それはまったく同じ観測された動作(前者の場合)またはそれ以外の同様に望ましくない動作(後者の場合)のいずれかにつながります。
currentページwithinを取得するにはどうすればよいですか? sink
閉鎖?
あなたの意図から私が理解していることは、あなたが小文字&[〜#〜]大文字[ 〜#〜]ページ番号に関して。しかし、あなたはロジックをCombine
フレームワークの意図された仕事ではない範囲まで取り入れています。 @ user1046037 による質問のコメントの1つとして、
Combine
は突然変異ではありません。代わりに、時間の経過とともに値を変換するために使用する必要があります。
したがって、page
パブリッシャーの値の変更をトリガーするのはstring
サブスクライバーであってはなりません。代わりに、string
の値を意図的に変更します。次に、値をpage
にバインドされた目的のロジックに変換できます。そして、これらのロジックはオブジェクト自体に入る必要があります。私の意味を見てみましょう:
class StringPager {
@Published var page = 0
@Published var string = "lorem ipsum"
private var cancellableBag = Set<AnyCancellable>()
init() {
let publisher = $page
.map { [unowned self] in
return $0 == 1 ? self.string.lowercased() : self.string.uppercased()
}
publisher
.eraseToAnyPublisher()
.assign(to: \.string, on: self)
.store(in: &cancellableBag) // must store the subscriber to get the events
}
}
次に、ページの値を変更すると、その時点で文字列が保持する文字列値の予想されるケースバージョンが表示されます。
let pager = StringPager()
pager.$string.sink { print($0) }
pager.page = 1 // lorem ipsum
pager.page = 2 // LOREM IPSUM
pager.page = 3 // LOREM IPSUM
pager.page = 4 // LOREM IPSUM
pager.page = 1 // lorem ipsum
pager.page = 1 // lorem ipsum
文字列を以前の設定値以外の値に更新する必要がある場合は、ページ変換から独立しています。これは何を意味するのでしょうか?
pager.string = "new value" // new value
意図的にもう一度ページを設定するまで:
pager.page = 3 // NEW VALUE
問題は、実際にはCombineフレームワークの機能を使用していないことです。値の中でlookにsink
クロージャーを使用しないでください。それはCombineの全体のポイントを覆します!代わりに、作成するCombineパイプラインの一部として、その値flow into your sink
を許可する必要があります。
StringPagerから始めましょう。
class StringPager {
@Published var page = 1
@Published var string = "this is a test"
}
そして、文字列またはページ番号を設定するいくつかのテストボタンとともに、StringPagerを持つビューコントローラークラスがあるとします。
class ViewController: UIViewController {
let pager = StringPager()
var storage = Set<AnyCancellable>()
override func viewDidLoad() {
// ...
}
@IBAction func doButton(_ sender: Any) {
let i = Int.random(in: 1...4)
print("setting page to \(i)")
self.pager.page = i
}
@IBAction func doButton2(_ sender: Any) {
let s = ["Manny", "Moe", "Jack"].randomElement()!
print("setting string to \(s)")
self.pager.string = s
}
}
このテストベッドの概念がわかります。最初のボタンまたは2番目のボタンをクリックし、ページャーのページまたは文字列をランダムに新しい値に設定して、コンソールにそれを報告します。
ここでの目標は、文字列またはページが変更されるたびに、ページャーのページが1かどうかに応じて、ページャーの文字列の大文字または非大文字バージョンを吐き出すCombineパイプラインを作成することです。 viewDidLoad
で行います。複数のパブリッシャーのいずれかが変更されるのを監視し、それらのいずれかが変更されたときに現在の値を報告するCombine演算子はcombineLatest
なので、これを使用します。
pager.$page.combineLatest(pager.$string)
.map {p,s in p > 1 ? s.uppercased() : s}
.sink {print($0)}.store(in:&storage)
それでおしまい!次に、ボタンを数回タップして、何が得られるかを見てみましょう。
this is a test
setting page to 3
THIS IS A TEST
setting page to 3
THIS IS A TEST
setting page to 1
this is a test
setting string to Jack
Jack
setting string to Manny
Manny
setting page to 1
Manny
setting page to 1
Manny
setting page to 4
MANNY
setting string to Manny
MANNY
setting string to Moe
MOE
setting string to Jack
JACK
ご覧のとおり、ページまたは文字列を変更するたびに、パイプラインの終わりから新しい値が取得されます。これは正しい値です!ページが1より大きい場合は、大文字の文字列を取得します。ページが1の場合、文字列をそのまま取得します。任務完了!