脚本桥:结合 SBElementArrays

Mat*_*att 2 macos cocoa objective-c scripting-bridge

根据 Apple关于 Scripting Bridge performance文档,我们应该努力在 上使用批处理操作SBElementArrays,因为 Apple 事件调用很昂贵。

...只要有可能,您应该始终使用“批处理操作”数组方法之一,而不是枚举数组。这些方法避免了枚举的低效率,因为它们发送单个 Apple 事件而不是数组中的每个项目一个 Apple 事件。

我正在将 Scripting Bridge 与 System Events 应用程序一起使用,并且能够成功地从菜单中获取菜单项。它比我以前使用的 NSAppleScript 方法快得多。

我想要做的是将几个 SBElementArrays 组合在一起,每个 SBElementArrays 都包含来自不同菜单的菜单项。计划是然后运行批处理操作一次,而不是对每个菜单单独执行。

在我看来,这不应该那么复杂,尽管显然我在这方面的知识充其量是有限的。不幸的是,我遇到了严重的错误。

第一次尝试

如果我尝试创建一个空的 SBElementArray 元素,然后循环添加每组菜单项的菜单项,如下所示:

SBElementArray* menuItemCombinedArray = [[SBElementArray alloc] init];
for (SystemEventsMenuBarItem* menu in menuBar.menus) {
    menuItemCombinedArray = [[menuItemCombinedArray arrayByAddingObjectsFromArray:menu.menuItems] mutableCopy];
}

NSArray* menuItemNameArray = [menuItemCombinedArray arrayByApplyingSelector:@selector(name)];
Run Code Online (Sandbox Code Playgroud)

我收到一条错误消息[SBElementArray init] should never be used,这有点奇怪,因为 SBElementArray 是 NSMutableArray 的子类。

第二次尝试

接下来,我尝试了一种更黑客的方法,我为第一个菜单单独创建了 SBElementArray,然后循环遍历剩余的菜单并一次添加那些 SBObjects,如下所示:

SBElementArray* menus = menuBar.menus;
SystemEventsMenuBar* firstMenu = menus.firstObject;
SBElementArray* menuItemCombinedArray = firstMenu.menuItems;

[menus removeObjectAtIndex:0];

for (SystemEventsMenuBarItem* menu in menus) {
    SBElementArray* tempMenuItemsArray = menu.menuItems;
    for (int i = 0; i < tempMenuItemsArray.count; i++) {
        [menuItemCombinedArray addObject:[tempMenuItemsArray objectAtIndex:i]];
    }
}

NSArray* menuItemNameArray = [menuItemCombinedArray arrayByApplyingSelector:@selector(name)];
Run Code Online (Sandbox Code Playgroud)

但现在我得到一个不同的错误: [SBElementArray addObject:]: can't add an object that already exists.'

概括

从我阅读的搜索内容来看,总体上听起来像 Scripting Bridge,特别是 SBElementArray,有点不稳定。但是 Scripting Bridge 对我来说比 NSAppleScript 快得多,更接近我的目标。我想如果我能让这个优化工作,我会处于良好状态。

在此先感谢您的帮助!

foo*_*foo 5

SBElementArray不是一个数组——它是很多烟雾缭绕的 BS 掩盖了一个简单的事实,即 Apple 事件 IPC不是OOP,而是RPC 加上简单的关系查询

在所有这些SBElementArray渣滓之下,您真正​​拥有的是单个对象说明符,它描述了应用程序的 Apple 事件对象模型中“对象”之间的一对多关系,它是在编程用户界面中呈现的用户数据的理想化虚拟表示。

该应用程序还定义了各种苹果事件处理程序在其AEOM进行操作-创建,删除,移动,复制等-的想法是,你发送一个请求到应用程序,例如duplicate (every track whose artist is "Bjork") to (playlist "Icelandic"),接收处理程序计算出精确如何进行为你完成那个操作。

这种查询驱动的方法在实践中的效果如何取决于应用程序的 AEOM 支持的实现情况:通常底层模型层将集合实现为有序数组而不是无序集合,并且由于您基本上执行排序的集合操作更常见在 RDBMS 中可以看到,当相对于彼此移动数组元素时,有各种各样的机会出现乱序和其他错误。但基本概念并没有错(只是一个可靠实施的 PITA);唉,SB 作者似乎认为“关系图对于 Cocoa 用户来说太难了”(这对 CoreData 用户来说无疑是一个很大的惊喜),所以试着把它隐藏在一个臭臭的、无能的 ORM 下。

因此,尝试将NS[Mutable]Array语义应用于您正在做的问题是绝对没有意义的,因为SBElementArrays 不是本地(或远程)数组,而是围绕 AEOM查询的残缺混淆包装器。换句话说,要了解为什么您正在做的事情不起作用以及如何做到这一点,您需要了解 AEOM 的实际工作原理,SB 如何对其工作方式撒谎,以及 SB 如何将其谎言转化为 [非常有限] AEOM 行为。

因此,当您 apply 时-[SBElementArray arrayByApplyingSelector:],它实际上根本没有执行数组迭代;相反,它构建了一个表单对象说明符,|selector name| of |elements| of...并在get事件中将其发送到应用程序进行解析;结果是指定属性的值列表。当然,当您想要执行简单get操作以外的任何事情时,这一切都被证明是完全无用的,例如set (rating of every track of playlist "Icelandic") to 100,因为 SB API 过于残缺和偏见,无法让您表达这一点,即使它是一个完全有效的请求。

...

TL;DR:尝试在 SB 中做任何重要的事情完全是在浪费时间,因为你越用力推动它,它的伪面向对象伪造就越崩溃。该[正式支持]的方式做正确的Apple事件是通过AppleScript和像你说的使用AS通过NSAppleScript在腹股沟冲练习这仅比SB痛苦少(虽然部分,这将是因为你无疑会做错误,即通过字符串混搭生成自定义 AS 源代码并即时编译和执行它,而不是.scpt从应用程序包加载的预编译文件调用参数化处理程序)。

幸运的是,10.6 引入了 AppleScript-ObjC 桥,虽然它本身也有一些缺点,但它是迄今为止集成 AS 和 ObjC 代码的最简单快捷的方式,因为它允许您定义几乎出现在 ObjC 代码中的 AppleScript 脚本对象就好像它们是本地 Cocoa 类和实例一样。这将是我向您推荐的方法,除了琐碎的任务之外,不要忘记 SB(或者完全忘记它并坚持使用 AS,这可能是愚蠢的,但至少它大部分被理解,不那么不诚实的愚蠢)。