[macOS翻译]SwiftAutomation:理解Apple Events

124 阅读9分钟

本文由 简悦 SimpRead 转码, 原文地址 hhas.bitbucket.io

本章介绍基于事件的苹果应用程序脚本背后的主要概念。

本章介绍基于苹果事件的应用脚本背后的主要概念。

什么是 Apple 事件?

Apple 事件是一种基于消息的高级进程间通信(IPC)形式,用于在本地或远程应用程序进程之间(有时也在同一进程内)进行通信。

Apple 事件包含

  • 描述如何处理事件的几个预定义属性,如事件名称(由两个 OSType 值[1]指定)以及是否需要回复。

  • 向接收事件的事件处理程序提供零个或多个 参数

例如,当用户将一个或多个文件拖放到 Finder 中的 TextEdit.app 上时,Finder 会向 TextEdit 发送一个以文件标识符列表为直接参数的 aevt/odoc 事件,命令 TextEdit 打开该文件:

通过适当的绑定,编程语言也可以创建和发送 Apple 事件。例如,当客户端程序执行代码 iTunes().play() 时,一个 hook/Play 事件就会从客户端程序发送到 iTunes,指示它开始播放:

应用程序可通过向客户端程序发送回复事件来响应传入的 Apple 事件。如果有返回值,回复事件可能包含返回值;如果无法按要求处理事件,回复事件可能包含错误描述。例如,在客户端应用程序中执行命令 TextEdit().name.get() 会向 TextEdit 发送一个 core/getd 事件,该事件包含一个对象指定符,可识别其根 application 对象的 name 属性。TextEdit 会处理该事件,然后向客户端应用程序发送包含字符串 "TextEdit "的回复事件,并将其作为命令结果返回。这种交换通常是同步执行的,在用户看来就像一个简单的远程过程调用。也支持异步消息传递,但通常不用于桌面自动化。

什么是可编写脚本的应用程序?

可编写脚本(或 "AppleScriptable")应用程序是一种提供 Apple 事件接口供第三方(如最终用户)使用的应用程序。该应用程序实现了一个或多个事件处理程序来响应相应的事件,还可能支持 Apple 事件对象模型。虽然该接口可被视为 API,但其重点在于提供一个高级用户界面,该界面与应用程序可能拥有的其他用户界面(GUI、CLI、Web 等)是对等的,最终用户和开发人员都可以访问。

例如,iTunes.app 实现了两个用户界面,一个是图形界面,另一个是基于 Apple 事件的用户界面,这两个界面提供了基本相同的功能,但访问方式却大相径庭:

可编写脚本的应用程序还以 AETE 或 SDEF 资源的形式内置了脚本接口定义。这种资源可以通过编程方式获取并使用:

  • 支持将人类可读术语自动翻译为 AppleScript 和 SwiftAutomation 等高级桥接程序中的四字母代码

  • 通过 AppleScript Editor 等应用程序生成基本的人类可读文档。

(请注意,AETE 和 SDEF 格式并不能详尽无遗地描述应用程序的脚本接口,通常还需要额外的文档(即使不一定提供)才能形成对该接口的完整理解以及如何有效地使用它)。

什么是 Apple 事件对象模型?

苹果事件对象模型(AEOM)是一个[TO DO:'关系图']视图-控制器层,它提供了一个理想化的、用户友好的应用程序内部数据表示,允许客户端通过苹果事件来识别和操作该结构的各个部分。代表特定命令(get、set、move 等)的传入 Apple 事件会被解压缩,其参数列表中的任何对象指定符都会根据应用程序的 AEOM 进行评估,以确定该命令应执行的用户级对象。然后将命令应用于这些对象,并由 AEOM 将其转换为对应用程序执行级对象的操作。这些执行级对象主要是应用程序模型层中的用户数据对象,以及脚本编写者感兴趣的一些图形用户界面视图对象(如代表文档窗口的对象)。一个典型的可编写脚本的桌面应用程序的内部架构可能是这样的:

  • AEOM 将用户数据表示为一个对象图(名义上是树形),其节点通过一对一和/或一对多的关系连接起来。

  • AEOM 对象是通过高级查询(类似于 XPath 或 CSS 选择器)来识别的,而不是低级的链式方法调用。

  • 命令对对象进行操作,因此一条命令可能会对多个执行对象调用多个方法,以执行相对复杂的任务。

  • 当查询指定多个对象时,命令应在每个对象上执行相同的操作[2]。

  • AEOM 对象永远不会在桥上移动。当一条命令的结果中包含一个或多个 AEOM 对象时,其返回值是一个(或多个)查询,[希望]将来能识别这些对象,而不是 AEOM 对象本身。

(虽然苹果事件对象模型有时被第三方描述为类似于 DOM,但这是不准确的,因为 AEOM 的抽象程度比 DOM 高得多)。

AEOM 如何工作?

[待办事项:不妨将 AEOM "类 "与 Swift 协议进行比较,因为 AEOM 类实际上[不必]描述不同类型的对象,而是描述如何与这些对象进行交互--例如 "段落"。]

AEOM 是一个由对象组成的树状结构。这些对象可能包含描述性属性,如类、名称、id、大小或边界;例如

finder.version
itunes.playerState
textedit.frontmost

并可能 "包含 "其他对象:

finder.home
textedit.documents
itunes.playlists

然而,与 DOM 等其他对象模型不同的是,AEOM 中的对象是通过 关系 而不是简单的物理包含相互关联的。可以认为 AEOM 结合了过程式 RPC、面向对象的对象模型和关系数据库机制的各个方面。

对象之间的关系可以是一对一的:

finder.home
itunes.currentTrack

或一对多:

finder.folders
itunes.playlists

而关系通常遵循底层数据结构的包含结构:

textedit.documents

并非总是如此。例如,以下对象指定符都标识相同的对象(用户桌面上的文件):

finder.disks["Macintosh HD"].folders["Users"].folders["jsmith"].folders["Desktop"].files

finder.desktop.files

finder.files

虽然只有第一个指定符通过物理包含方式描述了文件的位置,其他两个指定符使用应用程序提供的其他关系作为方便的快捷方式。某些应用程序在解释和评估针对关系对象图的查询时,其灵活性令人惊讶:

finder.home.folders["Desktop"].files

finder.startupDisk.folders["Users:jsmith:Desktop:"].files

finder.items[URL(string: "file:///Users/jsmith/Desktop")].files

根据应用程序状态的变化,某些指定器可能会在不同时间识别不同的对象:

itunes.currentTrack

有些指定符可能会识别在应用程序底层模型层中并不作为独立对象存在的数据,而是描述如何在字符缓冲区或 SQLite 后备存储等其他内部数据结构中查找内容。例如

textedit.documents[1].text.characters

textedit.documents[1].text.words

textedit.documents[1].text.paragraphs

并不标识实际的字符单词段落对象实例,而是描述如何在单个NSTextStorage实例中定位一系列数据。AEOM 与模型层结构的这种脱钩使应用程序能够以一种方便用户(即易于理解和使用)的方式呈现数据。

最后,"一对多 "关系可以根据相关元素的单个类或共享超类,有选择性地识别相关元素的子集。例如

finder.items

识别所有属于 "item "类子类的对象(即磁盘、文件夹、文档文件、别名文件等)。

finder.files

识别所有属于 "file "类子类的对象(如文档文件、别名文件等)。

finder.documentFiles

仅识别 "文档文件 "类的所有对象。

了解应用程序的 AEOM 结构是成功操作 AEOM 的关键。为了说明上述概念,下面是一个简单的假设文本编辑器的 AEOM:

该程序的根对象是应用程序对象,而应用程序对象又与其文档和窗口对象具有一对多的关系。

每个文档对象与其包含的文本中的字符、单词和段落都是一对多的关系,而每个文档对象与其包含的文本中的字符、单词和段落又是一对多的关系,依此类推,直到无穷大。

最后,每个窗口对象与它所显示内容的文档对象之间都是一对一的关系。

SwiftAutomation 如何工作

SwiftAutomation 是对 macOS 的低级 Apple 事件管理器 API("NSAppleEventDescriptor")的高级纯 Swift 封装。

SwiftAutomation 架构由两部分组成:

  • 抽象类和协议扩展集合,提供面向对象的 API,用于构建关系型 AEOM 查询(对象指定器)和调度 Apple 事件(命令)

  • 命令行代码生成工具 "aeglue",可将这些抽象组件组合成具体类,用于构建对象说明符和发送命令。

此外,"aeglue "还可以使用目标应用程序的字典(AETE/SDEF 资源)为这些类添加人类可读的属性和方法,从而创建一个用户友好的高级界面,取代 Apple 事件管理器本身使用的隐晦的四字符代码("OSType")。

例如,以下 AppleScript 将 TextEdit 文件中每个非空段落的第一个字符设置为 24 pt:

tell application id "com.apple.TextEdit"
  set size of character 1 of (every paragraph where it  "\n") of every document to 24
end tell

下面是使用 SwiftAutomation 默认的 AE 胶水的 Swift 代码:

let textedit = AEApplication(bundleIdentifier: "com.apple.TextEdit")

let specifier = AEApp.elements("docu")
                     .property("ctxt")
                     .elements("cpar")[AEIts != "\n"]
                     .elements("cha ")[1]
                     .property("ptsz")
try textedit.sendAppleEvent("core", "getd", ["----": specifier, "data": 24])

并使用专为 TextEdit 生成的胶水类:

let textedit = TextEdit()

try textedit.documents.text.paragraphs[TEDIts != "\n"].characters[1].size.set(to: 24)

[1] OSType:32 位值,通常表示为 4 个字符的 C 文字,又称 "四字符代码"。用于 Carbon API,如苹果事件管理器。最好使用助记符值,例如'docu'='document'。

[2] 假设 AEOM 实现良好;实际上,大多数 AEOM 实现在成功处理复杂的多对象指定符方面都存在不同程度的局限性。这些限制通常没有记录在案,而是通过试验和错误发现的。


www.deepl.com 翻译