Reactivecocoa: `Action`のバインディングを制御します。

作成日 2016年10月04日  ·  22コメント  ·  ソース: ReactiveCocoa/ReactiveCocoa

コマンドパターンを使用するAPIは2つあります。 @sharpletが言ったように、これらはReactiveObjCから持ち込まれた可能性があります(例: button.rac_command

extension Reactive where Host: UIButton {
    public var pressed: MutableProperty<CocoaAction> { ... }
}

extension Reactive where Host: UIBarButtonItem {
    public var pressed: MutableProperty<CocoaAction> { ... }
}

強い参照を持つActionを所有するビューを持つのは間違っていると感じます。 Actionの所有権( CocoaActionを介して間接的に)は、元のレイヤー(VCまたはVM)に委ねる必要があります。

したがって、@liscio@sharpletとの長い議論の後、それを具体的にするために作られた新しいインターフェイス持っていることが提案されているActionと接続UIControlNSControl sのを。 コンセプトは次のようなものです。

extension Reactive where Base: UIControl {
    /// Create a trigger signal for a specific set of control events.
    public func trigger(for controlEvents: UIControlEvents) -> Signal<(), NoError>

    /// The action is weakly referenced internally.
    public func setAction<Input, Output, Error>(_ action: Action<Input, Output, Error>, for controlEvents: UIControlEvents, inputTransform: (Self, UIControlEvents) -> Input)

    public func removeAction()

    /// ... some convenience overloads of `setAction(_:for:inputTransform:)`.
}

このAPIを使用すると、UIControlおよびNSControlサブクラスからすべてのActionバインディングターゲットを削除します。これは、APIがそれらすべてをカバーしている必要があるためです。

より広い意味では、 @IBAction@IBOutlet背後にあるターゲットアクションパターンを模倣します。

Interface Builderでは、UIButtonの_Sent Action_をUIViewControllerの_Received Action _( @IBActionメソッド)にバインドでき、UIButtonは弱く参照されているターゲットにメッセージを送信します。

この場合、コントロールのイベント信号のコレクションを公開します。 Actionをバインディングターゲットとして使用すると、 Action@IBActionと同様の役割を持つ可能性があります。 例えば:

// Received Action <~ Sent Action
commitAction <~ confirmButton.touchUpInside

// The reverse `isEnabled` binding is now explicit & optional.
confirmButton.isEnabled <~ commitAction.isEnabled

// OR

confirmButton.setAction(commitAction, for: .touchUpInside)

これは

confirmButton.rac_pressed.value = CocoaAction(commitAction)

しかし、弱いものから弱いものへのバインディングセマンティクスを介して所有権が分離されています。

enhancement

最も参考になるコメント

それならそれを隠すつもりです。 単純にする。

全てのコメント22件

同等の機能のためにこれも必要になることを忘れないでください:

confirmButton.rac.isEnabled <~ commitAction.isEnabled

うーん、私はそれを見落としました。 それならActionオールインする必要があるようです。

@andersioそれが何を意味するのか、なぜこの問題が解決されたのか

@sharpletがもたらすものにisEnabledインレットへの値の伝播を分離するのは問題ないと思います。

私の意見では、便利な方法/パターンでその事実を隠すことは、私たちをそれほど購入していません。

強い参照を持つアクションを所有するビューを持つことは、単に間違っていると感じます。 アクションの所有権(CocoaActionを介して間接的に)は、元のレイヤー(VCまたはVM)に任せる必要があります。

私は同意しないと思います。 なぜそれがあなたにとって間違っていると感じるのですか?

弱い参照は、提案されたAPIのごく一部であり、コマンドパターンを使用して( Action介して)すべてのコントロールをカバーするUIControlメソッドの単一のセットをプッシュします。

Rexでは、特定の実装を備えたUIButton専用でした。

ActionであるAFAIKは通常、コマンドを表し、ビューモデルによって所有され、その状態からの内部依存関係と外部依存関係の両方を持っている可能性があります。 Cocoaのターゲットアクションパターンのように、所有者が明らかな場合に、なぜそれが強力な参照になる必要があるのか​​わかりません。

強力な参照を使用するRexAPIは、 CocoaAction動作のアーティファクトである可能性があります。これは、値をラップされたAction伝播できるように保持する必要があります。

ここで提案されているAPIは、代わりにAction直接使用します。

レックス0.12:

extension UIButton {
    public var rex_pressed: MutableProperty<CocoaAction> { get }

    // This one would become `BindingTarget`. Irrelevant to this issue.
    public var rex_title: MutableProperty<String> { get }
}

提案(すべてのUIControl ):

extension Reactive where Base: UIControl {
    /// The action is weakly referenced internally.
    public func setAction<Input, Output, Error>(
        _ action: Action<Input, Output, Error>,
        for controlEvents: UIControlEvents,
        inputTransform: (Self, UIControlEvents) -> Input
    )

    public func removeAction()

    /// ... some convenience overloads of `setAction(_:for:inputTransform:)`.
}

一般的に、UI状態には3つのクラスがあります。1つはActionのコマンドパターン専用です。

  1. 観察すべき手段や値がない可変キー。
    例: UIControl.isEnabledUILabel.textUIView.isHidden

BindingTargetとしてモデル化します。

  1. イベントの制御、およびメソッド呼び出しの通知。
    例: UIControl.trigger(for: .valueChanged)UITableViewCell.trigger(for: #selector(prepareForReuse))

Signalとしてモデル化します。

  1. 可変キー+ユーザーの操作時に投稿する対応する制御イベント。
    例: UITextField.textNSTextField.textUISwitch.isOn

#3229

  1. [ _ Actionをコントロールに関連付けます。
    (これは、[1]と[2]の上に構築された便利なパターンです。)
    例: UIButton.rac_command

_この問題。 _

私は残念ながら考える@andersio removeAction()また、私はそれを名前を変更したいあなたがしているだけで1.0 _or_、のアクションを削除するイベントを制御内容を指定する必要がありますremoveAllActions()とにメモを追加setAction()は、コントロールが現在、この「簡単なAPI」を使用してフックされている単一のアクションのみを処理することを示します。これは、とにかく、99.9%の人がそれを使用する方法だからです。

それは議論の余地があります。 この場合、複数のアクションがisEnabled同時に操作できるため、IMOの複数のアクションは意味をなさない可能性があります(=ちょっと不確定)。

与えられたアクションを直接設定する(Swift)プロパティを追加してみませんか?

extension Reactive where Base: UIButton {
    var pressed: CocoaAction {
        get { … }
        set { … }
    } 
}

それとも、それがsome convenience overloadsセクションに含まれていましたか?

汎用APIを追加するのは問題ないと思いますが、便利なAPIの方が重要だと思います。 それが私が世界に披露するAPIです。 私はこのようなものが非常に説得力があると思います:

button.reactive.pressed = CocoaAction(viewModel.action)

setAction(…)を使用している限り、削除はおそらくnilアクションを設定して行う必要があります。 別のAPIは必要ありません。

これは間違いなく良く見えます、ええ。 これらは間違いなく保管する必要があります。 CocoaActionActionへの弱参照を持たせることを提案します。

setActionが更新され( 790b3e8 )、 pressedが戻ってきました( 790b3e8 )。

extension Reactive where Base: UIButton {
    public var pressed: CocoaAction<Base> { get nonmutating set }
}

extension Reactive where Base: UIControl {
    public var action: CocoaAction<Base>? { get }
    public var controlEventsForAction: UIControlEvents? { get }

    /// `CocoaAction` is retained by `self`, but `CocoaAction` weakly references `Action`.
    public func setAction(
        _ action: CocoaAction<Base>?,
        for controlEvents: UIControlEvents
    )
}

APIは複数のアクションをサポートする必要があると思います。 したがって、これは有効である必要があります。

button.reactive.setAction(action1, for: .touchUpInside)
button.reactive.setAction(action2, for: .touchUpOutside)
// button now has 2 actions set for 2 different events

CocoaAction Action弱く参照するのはなぜですか?

@mdiep

// button now has 2 actions set for 2 different events

つまり、 buttonの有効化状態は、 action1.isEnabledaction2.isEnabled両方にバインドされます。 これが私たちが望むものかどうかはわかりません。 有効化状態はどのように決定する必要がありますか? すべてのActionの有効化状態のAND

CocoaActionがActionを弱く参照するのはなぜですか?

それを元に戻しました。 今のところ(そしてレックスの歴史の中で)それが問題を引き起こさないことを認めます。

これは、ボタンの有効化状態がaction1.isEnabledとaction2.isEnabledの両方にバインドされていることを意味します。 これが私たちが望むものかどうかはわかりません。 有効化状態はどのように決定する必要がありますか? すべてのアクションの有効化状態のAND?

そして、私には正しくないようです。

オプションは次のとおりです。

  1. 完全に有効にすることをあきらめる
  2. ANDすべてのアクションの有効な状態
  3. またはすべてのアクションの有効な状態
  4. アクションが1つだけの場合に有効にする
  5. setAction(:for:)直接有効にするのではなく、 .pressedなどで有効にします

(5)私にとって最良の解決策のようです。 一般的なケースは簡単で便利です。 もっと複雑なことをしたい場合でも、それは可能です。

(5)は、 setActionが複数のactionN <~ reactive.trigger(for: .specificEvent)実行するのと違いがないことを意味します。 さらに、コントロールに複数の便利なコマンドスロットが含まれている場合はどうなりますか?

それはまだ異なります:

  1. キャンセルは異なります
  2. イベントごとに1つのアクションのみを許可します
  3. CocoaActionへの入力としてコントロールを渡します

そうでなければ、それらはほぼ同じです、はい。 でも大丈夫だと思います。

複数のアクションをサポートする計画の場合は、おそらく名前をaddAction変更する必要があります。

propagateEnablingState引数をsetActionに追加した場合でも、 isEnabledベクトルを処理する方法を決定する必要があるとします。 (構成可能?例および/または/未定義?)

複数のアクションを必要とする人々のために別の使用法( trigger(for:)アクションを個別にトリガーする)を残しているため、ここで説明している「コンビニエンスプロパティ」で> 1アクションをサポートする価値があるかどうかはわかりません。

使いやすい代替手段を考えると、この方法でボタンに複数のアクションを追加する正当な理由はわかりません(これは、他のSignal / BindingTargetのものの使用法とも一致します)。

基本的に、「単純なAPI」が必要な場合は、ここのようにactionプロパティを使用します。 その有効な状態はボタンに反映されます。

あなたが> 1アクションにボタンを添付する場合は、個々の使用trigger sのを、その後、フックアップmapにフックするために、独自のをisEnabled BindingTarget 。 それは合理的に聞こえますか?

基本的に、「単純なAPI」が必要な場合は、ここのようにactionプロパティを使用します。 その有効な状態はボタンに反映されます。

ボタンを1つ以上のアクションにアタッチする場合は、個々のトリガーを使用してから、独自のマップをフックしてisEnabledBindingTargetにフックします。 それは合理的に聞こえますか?

それは私には合理的に聞こえます。

では、 setAction(:for:)が必要なのでしょうか。

それならそれを隠すつもりです。 単純にする。

このページは役に立ちましたか?
0 / 5 - 0 評価