web-dev-qa-db-ja.com

@Publishedプロパティを組み合わせる:更新中に他の場所から現在の値を取得する

私の主な問題は、サブスクライバーに変更が通知されるまで@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にはありませんまだ更新された値なので、サブジェクトは間違ったブランチを実行します。

シンクする前に件名をZipingしてpager.$pageを修正してみました:

stringSubject.Zip(pager.$page).eraseToAnyPublisher().sink { ...same code... }

同様にcombineLatesting:

stringSubject.combineLatest(pager.$page).eraseToAnyPublisher().sink { ...same code... }

しかし、それはまったく同じ観測された動作(前者の場合)またはそれ以外の同様に望ましくない動作(後者の場合)のいずれかにつながります。

currentページwithinを取得するにはどうすればよいですか? sink閉鎖?

4
Elliot Schrock

あなたの意図から私が理解していることは、あなたが小文字[〜#〜]大文字[ 〜#〜]ページ番号に関して。しかし、あなたはロジックを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
1
nayem

問題は、実際にはCombineフレームワークの機能を使用していないことです。値の中でlooksinkクロージャーを使用しないでください。それは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の場合、文字列をそのまま取得します。任務完了!

0
matt