Test results Latest release swift 5.0 shield swift 5.1 shield swift 5.2 shield swift dev shield Platforms: macOS, iOS, watchOS

动作 (Actions)

用于处理 Swift 应用程序中动作的抽象概念。

稳定性

请注意,目前的 API 仍在变动中。

尽管我使用了语义化版本号,但在一切稳定下来之前,我会滥用它们 - 因此,新的维护版本可能包含破坏性更改。这仅仅是为了避免过早地到达 10.x 版本!

概念

动作是执行某些工作的离散片段。这可能是修改模型,或执行用户界面动作,实际上并不重要。

动作使用标识符向 ActionManager 注册。然后通过 ActionManager 使用相同的标识符来调用它们。

动作彼此之间以及与其不需要执行其特定任务的任何事物之间解耦。

当执行一个动作时,它会传递一个上下文。其中包含它执行其动作所需的所有信息,并且是确保耦合松散和动态的主要机制。

提供给动作的上下文由响应链中的项目填充。 这样,它实际上取决于用户界面上下文 - 哪个窗口位于前面,哪个项目具有焦点等等。 只要响应链中的某些内容提供正确的上下文,就可以在许多不同的情况下调用相同的操作。 Swift 的类型安全在这里有所帮助,可以轻松地从上下文中提取相关参数,确保它们是正确的类型。

UI 集成

Actions 模块不依赖于 AppKit/UIKit。 它可用于实现命令行应用程序的动作,或在非 Apple 平台上实现动作。

另一方面,ActionsKit 模块构建在 Actions 之上,并将其集成到 AppKit 或 UIKit 的响应链中。

这允许您将 UI 按钮、菜单等绑定到将 performAction 选择器发送到响应链,并让动作管理器选择它们,推断要执行的动作并执行它。它还实现了一些验证支持。

尚未提供撤销支持,但将会添加。

用法

设置

创建一个 ActionManager,将其附加到全局对象(例如,您的应用程序委托),并向其注册一些动作。

如果要将 UI 项目绑定到它,请使用 ActionManagerMacActionManagerMobile 子类之一,并通过调用 installResponder 将其挂钩到响应链中。

class Application: NSObject, NSApplicationDelegate {
let actionManager = ActionManagerMac()

func applicationWillFinishLaunching(_ notification: Notification) {
    actionManager.register([
        MyAction(identifier: "MyAction"),
        AnotherAction(identifier: "AnotherAction")
    ])
    actionManager.installResponder()
}

调用

将用户界面对象的动作设置为 performAction(_ sender: Any),并将目标设置为第一响应者。将 UI 项目的标识符设置为要调用的动作的标识符。

或者,使用 actionManager.perform("MyAction") 直接调用动作。

动作 (Actions)

动作是类。

要定义一个动作,从 Action 继承,并实现 perform

class MyAction: PersonAction {
    override func perform(context: ActionContext) {
        // do stuff here
    }
}

上下文

传递给 performcontext 包含原始的 sender。

它还包含一个其他信息的字典。 响应链中的项目可以在执行动作时向此字典添加项目,方法是实现 ActionContextProvider 协议。

这使视图控制器或窗口控制器可以将基本信息传递给动作,同时保持它们完全解耦。

要调用的动作可以显式传递,也可以从用户界面项目的标识符中解析出来。

用户界面标识符可以采用以下形式:{prefix.}action{("key": "value", "key2": "value2")}

可选的前缀(将被丢弃)可以是任何字符串,并且可以包含句点。

这允许您将多个用户界面项目绑定到同一个动作,而无需为它们提供完全相同的标识符字符串(如果它们不是唯一的,Xcode 会报错)。 以下所有标识符都将调用 MyAction 动作:MyActionbutton.MyActionmenu.MyActionsome.other.thing.MyAction

如果动作后存在括号,则其内容被解释为要添加到上下文的键值对列表。 例如,两个按钮可能具有标识符 MyAction("color": "red")MyAction("color": "blue")。 两者都会调用 MyAction 动作,但 context["color"] 的值将分别设置为 redblue

目前,这些参数的解析方式就像它们是一个 JSON 字典一样,因此键和值都需要用引号引起来。 Xcode 实际上抱怨标识符中存在 ": 字符,因此未来的版本可能会取消此限制,并允许您简单地指定 (key: value, key2: value2)

验证

动作通常仅在某些情况下有效 - 例如,当选择了一些文本时 - 或者它们希望根据上下文更改其名称或可见性。

要执行验证,请覆盖 validate(context: ActionContext) -> Validation 并检查传入的上下文。

使用 Mac 或 iOS 动作管理器时,会自动为某些用户界面动作调用验证。 在其他情况下,您可以根据需要手动调用它。 自动验证几乎总是合适的,但是如果您想跳过它的情况,可以通过安排上下文包含 skipValidation 的 true 值来实现。

动作观察者

如果您的用户界面想知道何时执行了某些动作,则此模式可能很有用。

为您的动作的观察者定义一个协议。 这可以包含您需要的任何东西。

在要观察的用户界面控制器中实现您的协议。 还要实现 ActionContextProvider 协议,并将控制器附加到动作将读取的键。

protocol MyActionObserver {
  func myMethod(myArgument: String)
}

extension MyViewController: ActionContextProvider, MyActionObserver {

func provide(context: ActionContext) {
    context.append(key: "MyActionObserver", value: self)
}

在该动作中,除了执行实际工作之外,还要枚举观察者键。 对于每个观察者,调用协议中的一个方法,传递任何相关的参数或上下文。

class MyAction: PersonAction {
    override func perform(context: ActionContext) {
        // do some stuff here
        
        // notify observers
        context.forEach(key: "MyActionObserver") { (observer: MyActionObserver) in
            observer.myMethod(myArgument: "myValue")
        }
    }
}