简介
使用 AppleEvents,即用于自动化的 JavaScript (JXA) 所基于的 IPC 技术,您从另一个应用程序请求信息的方式是向它发送一个“对象说明符”,它的工作原理有点像用于访问对象属性的点表示法,有点像 SQL 或 GraphQL 查询。
接收应用程序评估对象说明符并确定它引用哪些对象(如果有)。然后它返回一个表示引用对象的值。如果引用的对象是对象的集合,则返回的值可以是值列表。对象说明符也可以指对象的属性。返回的值可能是字符串、数字,甚至是新的对象说明符。
对象说明符
用 AppleScript 编写的完全限定对象说明符的示例是:
a reference to the name of the first window of application "Safari"
在 JXA 中,将表示相同的对象说明符:
Application("Safari").windows[0].name
要向 Safari 发送 IPC 请求以要求它评估此对象说明符并以值响应,您可以在对象说明符上调用 .get() 函数:
Application("Safari").windows[0].name.get()
作为.get()函数的简写,您可以直接调用对象说明符:
Application("Safari").windows[0].name()
向 Safari 发送单个请求,并返回单个值(在本例中为字符串)。
通过这种方式,对象说明符在访问对象属性时有点像点符号。但是对象说明符比这更强大。
收藏
您可以有效地对集合执行映射或理解。在 AppleScript 中,这看起来像:
get the name of every window of Application "Safari"
在 JXA 中它看起来像:
Application("Safari").windows.name.get()
即使这请求多个值,它也只需要向 Safari 发送一个请求,然后它会遍历自己的窗口,收集每个窗口的名称,然后发回一个包含所有名称的列表值字符串。不管 Safari 打开了多少个窗口,这个语句只会产生一个请求/响应。
For 循环反模式
将该方法与 for 循环反模式进行对比:
var nameOfEveryWindow = []
var everyWindowSpecifier = Application("Safari").windows
var numberOfWindows = everyWindowSpecifier.length
for (var i = 0; i < numberOfWindows; i++) {
var windowNameSpecifier = everyWindowSpecifier[i].name
var windowName = windowNameSpecifier.get()
nameOfEveryWindow.push(windowName)
}
这种方法可能需要更长的时间,因为它需要length+1 次请求才能获取名称集合。
(请注意,集合对象说明符的 length 属性经过特殊处理,因为 JXA 中的集合对象说明符试图表现得像原生 JavaScript 数组。在长度属性上不需要(或不允许)调用 .get()。)
过滤,以及为什么您的代码示例很慢
AppleEvents 真正有趣的部分是所谓的“谁的子句”。这允许您提供标准来过滤从中返回值的对象。
在您的问题中包含的代码中,tasks 是一个对象说明符,它指的是已使用 who 子句过滤为仅包含未完成任务的对象集合。请注意,此时这仍然只是参考;直到你在对象说明符上调用.get(),它只是一个指向某个东西的指针,而不是这个东西本身。
然后您包含的代码实现了 for-loop 反模式,这可能就是您观察到的性能如此缓慢的原因。您正在向 OmniFocus 发送 length+1 请求。每次调用 .name() 都会导致另一个 AppleEvent。
此外,您每次都要求 OmniFocus 重新过滤任务集合,因为您每次发送的对象说明符都包含 who 子句。
试试这个:
var taskNames = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false}).name.get()
这应该向 OmniFocus 发送一个请求,并返回每个未完成任务的名称数组。
另一种尝试的方法是让 OmniFocus 评估“whose 子句”一次,并返回一个对象说明符数组:
var taskSpecifiers = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})()
遍历返回的对象指定数组并在每个对象上调用 .name.get() 可能会比原来的方法更快。
回答
虽然 JXA 可以获取对象集合的单个属性的数组,但似乎由于作者的疏忽,JXA 不支持获取集合中所有对象的所有属性。
因此,要回答您的实际问题,使用 JXA,没有办法一次将一批对象及其所有属性读入内存。
也就是说,AppleScript 确实支持它:
tell app "OmniFocus" to get the properties of every flattened task of default document whose completed is false
使用 JXA,如果您真的想要对象的所有属性,则必须回退到 for-loop 反模式,但我们可以通过将其评估拉到 for 之外来避免多次评估 who 子句循环:
var tasks = []
var taskSpecifiers = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})()
var totalTasks = taskSpecifiers.length
for (var i = 0; i < totalTasks; i++) {
tasks[i] = taskSpecifiers[i].properties()
}
最后,应该注意的是,AppleScript 还允许您请求特定的属性集:
get the {name, zoomable} of every window of application "Safari"
但是 JXA 无法发送对一个对象或对象集合的多个属性的单个请求。