2

在为我的 Mac 程序添加脚本功能时,我正在努力解决从索引数组中删除项目的常见编程问题,在该索引数组中,项目索引因删除项目而发生变化。

假设我的应用程序维护一个数据存储,其中存储了“Person”类型的对象。在 sdef 中,我定义了 Cocoa KeyallPersons来访问这些元素。我的应用声明了一个NSArray *allPersons.

到目前为止,它运作良好。例如,这个脚本运行良好:

repeat with p in every person
  get name of p
end repeat

当我想支持删除项目时,问题就开始了,如下所示:

repeat with p in (get every person)
  delete p
end repeat

(我意识到我可以只写“删除每个人”,这很好,但我想展示“重复”如何使事情变得更复杂)。

这不起作用,因为即使在删除其中一些项目之后,AppleScript 仍继续使用原始项目编号来引用这些项目,这自然会改变项目及其编号。

所以,考虑到我们有 3 个人,“Adam”、“Bonny”和“Clyde”,这将发生:

get every person
    --> {person 1, person 2, person 3}
delete person 1
delete person 2
delete person 3
    --> error number -1719 from person 3

删除第 1 项(亚当)后,其他项目重新编号为第 1 项和第 2 项。第二次迭代删除第 2 项(现在是克莱德),第三次迭代尝试删除第 3 项,该第 3 项不再存在那一点。

我该如何解决这个问题?

我可以强制脚本引擎不使用它们的索引号而是使用它们的唯一 ID 来处理这些项目,这样就不会发生这种情况吗?

4

3 回答 3

3

这不是您的 ObjC 代码,而是您对repeat with VAR in EXPR循环如何工作的误解。(这也不是你的错:它们 1. 违反直觉,2. 解释不清。)当它第一次遇到你的repeat语句时,AppleScript 会向你的应用程序发送一个count事件以获取由 指定的项目数EXPR,在这种情况下是一个对象标识任何内容的所有person元素的说明符(查询) 。然后,它使用该信息生成自己的按索引对象说明符序列,从 1 计数到上述结果count

person 1 of whatever
person 2 of whatever
...
person N of whatever

您需要意识到的是,对象说明符是第一类查询,而不是对象指针(Apple 也没有告诉您):它描述的是请求,而不是对象。忽略被盗用的行话:Apple 事件 IPC 最亲近的亲戚是 RDBMS,而不是 Cocoa 或 SOAP 或任何现代开发人员如此关注的 OO 消息传递杂物,即“做事的唯一方法”……好吧,一切

只有当该查询在 Apple 事件中发送到您的应用程序时,它才会根据您的 Apple 事件 IPC 视图控制器(也称为“Apple 事件对象模型”)的关系图进行评估 – 呈现为模型用户的理想化、用户友好表示它实际解析为特定模型对象或对象的日期,事件处理程序应使用该对象执行请求的操作。

因此,当delete您的循环中的命令repeat告诉您的应用程序时delete person 1 of whatever,您所有剩余的元素都会向下移动一个。但是在下一次迭代中,repeat循环仍然会生成对象说明符person 2 of whatever,然后您的脚本会将其发送到您的应用程序,该应用程序将其解析为集合中的第二项——当然,这最初是第三项,直到您将它们全部转移.

或者,借用一句话:

除了关系查询之外,AppleScript 中没有任何意义。

..

事实上,Apple 事件的基于查询的方法实际上很有意义,因为它最初被设计为在非常高延迟的连接上高效(即 System 7 的效率极低的进程切换器),允许单个 Apple 事件携带一个或多个一次操作多个对象的复杂查询。它甚至非常优雅[当它工作正常时],但被库比蒂诺的白痴完全取消了,他们认为让程序员不讨厌这项技术的最好方法是更加努力地说明它如何工作的。

所以在这里,我建议你去阅读这个,这也不是最好的解释,但仍然比你从那些布偶那里得到的任何东西都要好。并且,的原始设计者解释了创建高级粗粒度基于查询的 IPC 系统而不是通常的低级细粒度 OO 消息传递废话的许多基本原理。

哦,一旦你这样做了,你可能要考虑尝试运行它:

delete every person whose name is "bob"

这几乎是创建一个厚的声明性抽象的全部意义,它可以完成所有工作,因此用户不必这样做。

当除了命令式客户端循环之外什么都不会做时,您要么想先从应用程序获取按 ID 对象说明符(这是最接近 AEOM 可以做的安全、持久指针的事情)列表,然后再迭代那,或者至少使用您自己的迭代器循环来反向计算元素:

repeat with i from (count every person) to 1 by -1
    tell person i
        ..
    end tell
end repeat 

因此,假设它在服务器端迭代一个有序数组,将从最后一个到第一个删除,从而避免原始脚本的令人尴尬的 N 个错误。

高温高压

于 2016-04-12T14:07:07.910 回答
2

回复:“如果您希望您的可脚本元素可删除,请确保使用 NSUniqueIDSpecifiers 来识别它们。”

是的,Apple 建议使用 formUniqueId 或 formName 作为对象说明符,但您不能总是这样做。例如,在 Text Suite 中,您实际上只有索引可以使用;例如字符 1、单词 3、第 7 段等。您没有文本元素的唯一 ID。除了删除之外,排序还会受到其他 Standard Suite 命令的影响:打开、关闭、复制、制作和移动。

应用程序实现者是程序员,但脚本编写者也是。因此,期望脚本编写者自己解决一些问题是合理的。例如,如果应用程序有 5 个人,并且脚本编写者想要删除第 2 个人和第 4 个人,即使使用索引删除,他们也可以轻松做到:

delete person 4
delete person 2

从有序列表的末尾向前删除可以解决问题。AS 还支持负索引,可用于相同目的:

delete person -2
delete person -4
于 2016-04-30T23:40:08.413 回答
1

解决这个问题的关键在于objectSpecifier正确实现该方法,以便它确实返回一个NSUniqueIDSpecifier.

到目前为止,我的代码只返回一个索引说明符,为此目的是错误的。我想如果我发布了我的代码(不幸的是,这太复杂了),有人可能会注意到我的错误。

所以,我想规则是:如果你希望你的可脚本元素是可删除的,请确保你使用NSUniqueIDSpecifiers 来识别它们。但是,对于只读元素数组NSIndexSpecifier,如果您的元素数组具有持久的排序行为,则使用(可能)是安全的。

更新

正如@foo 指出的那样,命令通过 using而不仅仅是repeat获取对项目的引用也很重要,因为只有前者导致通过它们的 id 来寻址项目,而后者将它们保持为.… in (get every person)… in every personitem N

于 2016-04-12T14:07:24.897 回答