Reactivecocoa: `Action`s 的控制绑定。

创建于 2016-10-04  ·  22评论  ·  资料来源: ReactiveCocoa/ReactiveCocoa

有两个 API 使用命令模式。 正如@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进行了长时间的讨论后,建议有一个专门为ActionUIControlNSControl连接的新界面。 这个概念是这样的:

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入口。

在我看来,将这一事实隐藏在方便的方法/模式中并没有给我们带来太多好处。

让视图拥有具有强引用的 Action 感觉是错误的。 Action 的所有权(间接通过 CocoaAction)应该留给它的起源层(无论是 VC 还是 VM)。

我不认为我同意。 为什么你会觉得不对?

弱引用是提议的 API 的一小部分,它推送一组UIControl方法,这些方法涵盖使用命令模式(通过Action )的所有控件。

在 Rex 中,它是UIButton独有的,并具有特定的实现。

AFAIK, Action通常代表一个命令,由视图模型拥有,并且可能具有来自/对其状态的内部和外部依赖关系。 就像 Cocoa 目标-动作模式一样,我不明白为什么当它有一个明显的所有者时它应该是一个强有力的参考。

使用强引用的 Rex API 可能是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 s):

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 状态 + 一类专门用于Action的命令模式:

  1. 没有要观察的方法或值的可变键。
    例如UIControl.isEnabledUILabel.textUIView.isHidden

模型为BindingTarget s。

  1. 控制事件和方法调用通知。
    例如UIControl.trigger(for: .valueChanged) , UITableViewCell.trigger(for: #selector(prepareForReuse))

模型为Signal s。

  1. 可变键 + 在用户交互时发布的相应控制事件。
    例如UITextField.text , NSTextField.text , UISwitch.isOn

第3229章

  1. [ _Associating Action与控件。
    (这只是一个建立在 [1] 和 [2] 之上的便利模式。)
    例如UIButton.rac_command

_这个问题。 _

@andersio我认为不幸的是removeAction()还必须指定您要为其删除操作的控制事件,_或_仅适用于 1.0 我将其重命名为removeAllActions()并在setAction()添加注释

这是有争议的。 IMO 多个动作可能没有意义,因为在这种情况下,多个动作可以同时操作isEnabled (= 有点不确定)。

为什么不添加直接设置给定操作的 (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 。 我不确定这是否是我们想要的。 应如何确定启用状态? AND所有Action的启用状态?

为什么 CocoaAction 弱引用 Action?

恢复了它。 我承认它现在(以及在 Rex 的历史上)不会造成麻烦。

这意味着按钮的启用状态同时绑定到 action1.isEnabled 和 action2.isEnabled。 我不确定这是否是我们想要的。 应如何确定启用状态? 所有操作的启用状态的 AND ?

并且对我来说似乎不合适。

以下是我们的选择:

  1. 完全放弃启用
  2. 和所有动作的启用状态
  3. 或所有动作的启用状态
  4. 仅当有 1 个操作时启用
  5. .pressed等中启用,而不是直接在setAction(:for:)启用

(5) 对我来说似乎是最好的解决方案。 常见的情况是简单方便。 如果你想做一些更复杂的事情,你仍然可以。

(5) 意味着setAction与执行多个actionN <~ reactive.trigger(for: .specificEvent)没有区别。 此外,如果一个控件包含多个便利命令槽会发生什么?

它的不同之处在于:

  1. 取消不一样
  2. 每个事件只允许一个动作
  3. 它将控件作为输入传递给CocoaAction

否则它们大致相同,是的。 但我认为没关系。

如果计划支持多个操作,我们可能应该将其重命名为addAction

假设我们向setAction添加一个propagateEnablingState参数,我们仍然需要决定如何处理isEnabled的向量。 (可配置?例如和/或/未定义?)

我不确定是否值得在此处讨论的“便利属性”中支持 >1 个操作,因为我们已经为需要多个操作的人留下了替代用法(使用trigger(for:)单独触发操作)。

考虑到易于使用的替代方案(这也恰好与我们其他 Signal/BindingTarget 东西的使用相匹配),我看不出以这种方式向按钮添加多个动作的充分理由。

基本上,如果你想要一个“简单的 API”,你可以使用action属性,如下所示。 其启用状态反映在按钮中。

如果您想将按钮附加到 >1 个操作,那么您可以使用它们各自的trigger s,然后将您自己的map连接到isEnabled BindingTarget 。 这听起来合理吗?

基本上,如果你想要一个“简单的 API”,你可以使用这里的 action 属性。 其启用状态反映在按钮中。

如果您想将按钮附加到 >1 个操作,那么您可以使用它们各自的触发器,然后连接您自己的地图以连接到 isEnabled BindingTarget。 这听起来合理吗?

这对我来说听起来很合理。

那么我们是否需要setAction(:for:)呢?

那就藏起来吧把事情简单化。

此页面是否有帮助?
0 / 5 - 0 等级