有两个 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进行了长时间的讨论后,建议有一个专门为Action
与UIControl
和NSControl
连接的新界面。 这个概念是这样的:
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)
但是通过我们从弱到弱的绑定语义解耦了所有权。
不要忘记我们还需要它来实现等效的功能:
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
的命令模式:
UIControl.isEnabled
, UILabel.text
, UIView.isHidden
。模型为BindingTarget
s。
UIControl.trigger(for: .valueChanged)
, UITableViewCell.trigger(for: #selector(prepareForReuse))
模型为Signal
s。
UITextField.text
, NSTextField.text
, UISwitch.isOn
第3229章
Action
与控件。 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。
这无疑看起来更好,是的。 这些绝对应该保留。 我仍然建议让CocoaAction
对Action
有一个弱引用。
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.isEnabled
和action2.isEnabled
。 我不确定这是否是我们想要的。 应如何确定启用状态? AND
所有Action
的启用状态?
为什么 CocoaAction 弱引用 Action?
恢复了它。 我承认它现在(以及在 Rex 的历史上)不会造成麻烦。
这意味着按钮的启用状态同时绑定到 action1.isEnabled 和 action2.isEnabled。 我不确定这是否是我们想要的。 应如何确定启用状态? 所有操作的启用状态的 AND ?
并且对我来说似乎不合适。
以下是我们的选择:
.pressed
等中启用,而不是直接在setAction(:for:)
启用(5) 对我来说似乎是最好的解决方案。 常见的情况是简单方便。 如果你想做一些更复杂的事情,你仍然可以。
(5) 意味着setAction
与执行多个actionN <~ reactive.trigger(for: .specificEvent)
没有区别。 此外,如果一个控件包含多个便利命令槽会发生什么?
它的不同之处在于:
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:)
呢?
那就藏起来吧把事情简单化。
最有用的评论
那就藏起来吧把事情简单化。