Skip to content

Latest commit

 

History

History
1519 lines (1517 loc) · 68.9 KB

Making A Mac App Scriptable Tutorial.md

File metadata and controls

1519 lines (1517 loc) · 68.9 KB

让Mac App可脚本化的教程

16年9月21日更新: 这个教程已更新至Xcode 8及Swift 3。

Making a mac app scriptable tutorial: feature image

作为一个app开发者,几乎是不可能想到人们 所有的 使用你app的方法的。如果能让你的用户创建脚本,来定制你的app去满足他们个人化的需求,难道不是很酷?

使用Applescript和Javascript来自动化(JXA),你可以!在这个让mac app可脚本化的教程中,你将了解到,怎样添加脚本的处理的能力到示例应用上。你将从了解怎样使用脚本控制存在的app开始,然后扩展一个app去允许定制脚本行为。

开始吧

下载 示例工程 ,在Xcode中打开它,build并运行,查看它的样子:

Making a mac app scriptable tutorial: Scriptable Tasks app

这个app展示了一个带有到期日的,在接下来几天的任务的列表,还有关联到每个任务的标签。它使用一个outline view来根据到期日给任务分租。

注意: 想要了解更多关于outline view的事?在这个文章中查看 OS X的教程NSOutlineView

你可能注意到,你不能添加,编辑,或删除任务。这是由设计决定的 - 这些动作将通过你的用户的自动化脚本来处理。

看一下项目中的文件:

Making a mac app scriptable tutorial: Scriptable Tasks Project

  • 有两个模型类的文件: Task.swift Tag.swift 。这是你将要编写的类。
  • ViewController 这组处理展示和观察数据的变化。
  • Data 组有一个带有示例任务的文件,还有一个 DataProvider 来读取那些任务,并处理到达的任何修改。
  • 这个 AppDelegate 使用一个 DataProvider 对象来持有一个app任务的记录。
  • ScriptableTasks.sdef 是一个重要的文件...你将要在之后详细地探索。

本教程还有一些示例脚本; 从这里下载 。在这个包中有两个目录:一个用于AppleScript,另一个用于JavaScript。由于这个教程的重点不在于写脚本,你将使用每一个下载到的脚本来测试你将添加到 Scriptable Tasks 中的功能。

说得已经足够了 - 是时候移步到脚本这里了!:]

使用脚本编辑器

打开 Applications/Utilities 中的 Script Editor app ,并打开一个新的文档:

Making a mac app scriptable tutorial: Script Editor

你将在顶部的工具栏中看到四个按钮: Record Stop Run ,和 Compile 。Compile检查你的脚本是语法正确的,而Run做的大致就是你所期望的。

而在window的底部,你会看到三个图标,用来在view之间切换。 Description 让你添加一些关于你的脚本的信息,而 Result 则展示了你运行一个脚本最后的结果。最有用的选项则是第三个按钮: Log

而Log还提供了四个选项: Result Messages Events Replies 。Replies是信息量最大的,因为它展示了每个命令和命令返回的值。当测试任何脚本的时候,我强烈推荐 Log in Replies 模式。

注意: Note: 如果你曾经打开过一个AppleScript文件,并发现它包含类似下面的代码: «class TaSk» whose «class TrFa» is false and «class CrDa» ,点击 Compile ,它将转变为可读的AppleScript,前提是你已安装了目标的app。

在这个教程中,你将cover到两种脚本语言。第一种是AppleScript,它是在1991年的Mac系统7是被引入的,使其可以被码农和非码农使用。

第二种则是JavaScript for Automation (JXA),它是在OSX Yosemite系统中被引用的,让码农可以使用它们熟悉的JavaScript语法来构建它们的自动化任务。

在本教程中的脚本将同时通过AppleScript和JXA来展现,因此你可以自由地沿着你想要探索那种语言去漫游。:]

注意: 在本教程中,脚本的代码片段将首先通过AppleScript来展示,之后则紧跟着等效的JavaScript的版本。

使用TextEdit探索App脚本

有一个很棒的小app早已安装到了你的Mac上:TextEdit,它支持脚本的撰写。在 Script Editor 中,选择 Window/Library 并找到 TextEdit 的入口。如果它不在这里,单击顶部的 Plus 按钮,找到你的 Applications 目录并添加 TextEdit 。然后双击 TextEdit 入口来打开TextEdit字典:

Making a mac app scriptable tutorial: Text Edit Dictionary

每一个可编写脚本的app有一个字典,储存在脚本的定义文件(SDEF)中。这个字典告诉你这个app中包含哪些对象,这些对象中包含什么属性,这个app能响应什么命令。在上面的屏幕截图中,你可以看到TextEdit含有若干个段落,这些段落含有颜色和字体的特性。你将使用这些信息来风格化一些文本。

AppleScript JavaScript 目录中打开 1. TextEdit Write.scpt 目录。运行脚本;你将看到TextEdit创建并保存了一个文档。

现在你有了一个新的文档,但它需要一点样式。打开 2. TextEdit Read Edit.scpt ,运行这个脚本,你将看到这个文档根据脚本再次打开并样式化。

尽管深入钻研实际的脚本超过了这个教程的范围,也请随意仔细阅读脚本,来查看它如何在TextEdit的文档上执行。

正如在介绍中提到的,所有的app在一定程度上都是可脚本化的。为了看到这个起作用,确保Scriptable的任务正在执行。下一步,在Script Editor中打开一个新的脚本window并输入下列脚本中的一个,取决于你正在使用的语言:

-- AppleScript
tell application "Scriptable Tasks" to quit

// JavaScript
Application("Scriptable Tasks"). quit();

点击 Run ,应该可编写脚本的程序就退出了。改变脚本为下面的样子,并在此点击 Run

tell application "Scriptable Tasks" to launch

Application("Scriptable Tasks").launch();

这个app会重新启动,但不会到达前台。要使它成为“焦点”,在上面的脚本改变 launch activate ,并单击 Run

既然你已看到这个app可以响应脚本命令了,那就时候将这个能力添加到你的app上了。

让你的App可编写脚本

你的App的脚本定义文件定义了这个app可以做的事;这就有点像一个API。这个文件存在在你的app项目中,并指定了几件事情:

  • 标准的脚本对象和命令,例如 window make delete count open quit
  • 你自己的可编写脚本的对象,属性和自定义命令。

为了让你的app中的类可脚本化,你需要对其做出一些改变。

首先,脚本的接口使用Key-Value-Coding来get和set对象的property。在OC中,所有的对象都自动地遵守KVC协议,但Swift的对象并不是这样的,除非你使其成为 NSObject 的子类。

接下来,可脚本的类需要一个脚本接口可以识别的OC的名字。为了避免命名空间的冲突,Swift对象的名称是很难以给出一个独立的表示的。通过使用 @objc(YourClassName) 来给类添加前缀,你就给了它们一个可以被脚本引擎使用的名称。

可编写脚本的类,需要对象说明符协助在应用或父对象中定位一个特定的对象,最终,这个app的delegate必须能够访问的到储存数据的地方,这样它才能返回应用的数据到脚本中。

你并不必须从scratch来开始你自己的脚本定义文件,因为苹果提供了一个标注的SDEF文件供你使用。它就是 /System/Library/ScriptingDefinitions/ 目录中的 CocoaStandard.sdef 文件。在Xcode中打开它并查看;它是XML格式的,带有指定的header,其中还有一个字典,里面是标准的套件。

这时一个有用的起点,你 This is a useful starting point, and you 可以 拷贝并粘贴这个XML到你自己的SDEF文件中。然而,从干净的代码的角度考量,让SDEF文件充满你的app不支持的命令和对象并不是一个好主意。因此,到了最后,这个示例工程应当只包含起始的SDEF文件移除了全部非必需记录的部分。

关闭 CocoaStandard.sdef 并打开 ScriptableTasks.sdef 。在靠近结尾 Insert Scriptable Tasks suite here 注释的这里添加下列代码:

<!-- 1 -->
<suite name="Scriptable Tasks Suite" code="ScTa" description="Scriptable Tasks suite.">
  <!-- 2 -->
  <class name="application" code="capp" description="An application's top level scripting object.">
    <cocoa class="NSApplication"/>
 
    <!-- 3 -->
    <element type="task" access="r">
      <cocoa key="tasks"/>
    </element>
  </class>
 
  <!-- Insert command here -->
 
  <!-- 4 -->
  <class name="task" code="TaSk" description="A task item" inherits="item" plural="tasks">
      <cocoa class="Task"/>
 
      <!-- 5 -->
      <property name="id" code="ID  " type="text" access="r"
          description="The unique identifier of the task.">
          <cocoa key="id"/>
      </property>
 
      <property name="name" code="pnam" type="text" access="rw"
          description="The title of the task.">
          <cocoa key="title"/>
      </property>
 
      <!-- 6 -->
      <property name="daysUntilDue" code="CrDa" type="number" access="rw"
      description="The number of days before this task is due."/>
      <property name="completed" code="TrFa" type="boolean" access="rw"
      description="Has the task been completed?"/>
 
      <!-- 7 -->
      <!-- Insert element of tags here -->
 
      <!-- Insert responds-to command here -->
 
  </class>
 
  <!-- Insert tag class here -->
 
</suite>

这个XML的代码块做了很多的工作。一步一步来看:

  1. 最外层的元素是 suite ,所以你的SDEF文件现在有两个suite: Standard Suite Scriptable Tasks Suite 。在SDEF文件中的每件事都需要一个四字符的code。苹果的code几乎总是小写的,你将使用其中的几个用于特定的目的。对于你自己的suite,class和property,则最好使用大写、小写和符号的随机混合来避免冲突。
  2. 下一部分定义了应用,并必须使用code值 "capp" 。你必须制定application的class;如果你子类化了 NSApplication ,你就该在这里使用你子类的名称了。
  3. 这个application包含 element 。在这个app中,element被储存在app的delegate,一个叫做 tasks 的数组中。在脚本术语中,element是app和其它对象可以包含的对象。
  4. 最后的一个块定义了application包含的 Task class。访问多个的复数名称是 tasks 。在这个app中,支持这个对象类型的class是 Task
  5. 前两个property是特定的。请看它们的code: "ID " an和d "pnam" "ID " (注意字母之后的两个空格)指定了这个对象的唯一标识符。 "pnam" 指定了这个对象的 name property。你可以使用它们中的任一个,来直接访问对象。

    "ID " 是只读的,因为脚本不应该改变唯一标识符,但 "pnam" 是可读写的。它们都是text类型的property。 "pnam" property映射到了 Task 对象的 title property。

  6. 还剩两个property,一个是number类型的property daysUntilDue ,另一个是Boolean类型的property completed 。它们可以在对象和脚本中使用相同的名称,因此你不需要指定 cocoa key
  7. “Insert…”的注释是为你需要添加更多内容到这个文件时的占位符。

打开 Info.plist ,在记录下方的空白处右击,并选择 Add Row 。输入一个大写的 S ,将建议的列别滚动到 Scriptable 。选择它并将设置更改为 YES

重复这个过程来选择下一项: Scripting definition file name 。设置它为你的SDEF文件的文件名: ScriptableTasks.sdef

如果你喜欢以源码的形式编辑Info.plist,你也可以添加下列的记录到主字典中:

<key>NSAppleScriptEnabled</key>
<true/>
<key>OSAScriptingDefinition</key>
<string>ScriptableTasks.sdef</string>

现在你必须修改app的delegate,来处理从脚本中传来的请求。

打开 AppDelegate.swift 文件,并添加下列代码到文件的尾部:

extension AppDelegate {
  // 1
  override func application(_ sender: NSApplication, delegateHandlesKey key: String) -> Bool {
    return key == "tasks"
  }
 
  // 2
  func insertObject(_ object: Task, inTasksAtIndex index: Int) {
    tasks = dataProvider.insertNew(task: object, at: index)
  }
 
  func removeObjectFromTasksAtIndex(_ index: Int) {
    tasks = dataProvider.deleteTask(at: index)
  }
}

上面的代码执行了以下的事:

  1. 当一个脚本请求 tasks 的数据时,这个方法将确认app的delegate可以处理它。
  2. 如果一个脚本尝试插入,编辑或删除数据,这些方法将传递那些请求到 dataProvider 中。

为了使 Task 的model类对脚本可用,你必须在做一点coding。

打开 Task.swift ,并将类的定义修改成下面的样子:

@objc(Task) class Task: NSObject {

Xcode会立刻抱怨说 init 要求 override 关键字,所以让Fix-It来做吧。这是必需的,因为这个类现在有一个父类:

override init() {

Task.swift 需要更多的修改:一个对象说明符。插入下列的方法到 Task 的类中:

override var objectSpecifier: NSScriptObjectSpecifier {
  // 1
  let appDescription = NSApplication.shared().classDescription as! NSScriptClassDescription
 
  // 2
  let specifier = NSUniqueIDSpecifier(containerClassDescription: appDescription,
                                      containerSpecifier: nil, key: "tasks", uniqueID: id)
  return specifier
}

以此来对每个编号评论:

  1. 因为app是task的容器,获取app的类的描述。
  2. 通过id在app中获取任务的描述。这就是为什么Task类有一个 id 的property - 这样每个任务就可以被正确地指定。

你终于可以开始脚本化你的app了!

脚本化你的App

在你开始之前,确保退出任何这个app运行中的实例,它们可能是被Script Editor打开的。

Build并运行Scriptable Task;右击Dock上的icon,并在Finder中选择 Options/Show 。退出 Script Editor app并重启,让它对你的app做出的改变生效。

打开 Library 的window,并从 Finder 中拖拽 Scriptable Tasks Library window中。

如果你收到了一个错误,说这个app是不可编写脚本的,尝试退出Script Editor并重启它,因为它有时没有注册一个新build的app。如果它仍然没能成功地导入,请返回并仔细检查你对SDEF文件进行的修改。

双击Library中的 Scriptable Tasks 来查看app的字典:

Making a mac app scriptable tutorial: Scriptable Tasks Dictionary 1

你将看到Standard Suite和Scriptable Tasks Suite。单击 Scriptable Tasks suite,你将看到你在SDEF文件中添加了什么。这个应用包含任务,一个任务包含四个property。

使用工具栏中的 Language 弹出菜单,来改变目录中的脚本语言为 JavaScript 。你将看到基本相同的信息,但有一个重要的变化。class和property的case发生了改变。我不清楚这是因为什么,但他是那些你需要注意的“陷阱(gotchas)”之一。

Script Editor 中,创建一个新的脚本文件,并设置这个编辑器展示 Log/Replies 。测试下面的脚本之一,确保在语言的弹出菜单中选择恰当的语言:

tell application "Scriptable Tasks"
  get every task
end tell

app = Application("Scriptable Tasks");
app.tasks();

在log中,你将看到一个通过ID的task的列表。为了让信息更有用,编辑脚本就像下面这样:

tell application "Scriptable Tasks"
  get the name of every task
end tell

app = Application("Scriptable Tasks");
app.tasks.name();

Making a mac app scriptable tutorial: AppleScript Tasks

Making a mac app scriptable tutorial: JavaScript Tasks

再尝试一些你之前下载的脚本。当运行脚本的时候,确保你将Script Editor设置为 Log/Replies 这样你就能一路上看到结果。

每个脚本在重写运行它之前退出这个app;这是为了在任何的编辑之后重置数据,这样示例脚本就如同期望一般地工作。你通常不会在你自己的脚本中执行此操作。

注意: Script Editor会在你build更新版本的app时感到非常困惑,因为如果你有一个打开的正在使用这个app的脚本,它会尝试一直保持其同一版本来运行。这通常会以app的老版本形式结束,因此在每次build之前,退出app。

任何时候,如果你看到两个副本的Scriptable Tasks的app正在运行,或在任何示例中出现了脚本的错误,你可以确定,这是Script Editor已跑在了错误版本的app上。最简单的修复就是退出所有副本的app,并退出Script Editor。Clean Xcode的build( Product/Clean ),然后build并再次运行。

重新启动Script Editor,当它打开脚本的时候,单击 Compile ,然后单击 Run 。如果失败了的话,请在 ~/Library/Developer/Xcode/DerivedData 里删除app中的Derived Data。

尝试下面的两个示例脚本:

3. 获取Tasks.scpt

这个脚本使用各种过滤器检索任务的数量和任务的名称。记下下列事项:

  • JavaScript从0开始计数,而AppleScript从1开始计数。
  • 文本搜索是不区分大小写的。

4. 添加编辑Tasks.scpt

这个脚本添加了新的任务,在第一个任务上切换了 completed 的标记,并尝试创建另一个相同名称的任务。

嗯...创建一个同名的任务work了!现在你有了两个”喂猫“的任务。猫会很激动,但对于这个app的目的,任务的名称应当是唯一的。尝试添加一个名称早已存在的任务可能产生一个错误。

回到 Xcode ,查看 AppDelegate.swift ,你会看到当脚本想要插入一个对象时,app的delegate会将这个调用床底给 dataProvider 。在 DataProvider.swift 中,查看 insertNew(task:at:) ,它将一个存在的任务插入到数组中,或添加了一个新的任务到结尾。

到了在这里添加一次检查的时候了。用下列的代码来替换这个方法:

mutating func insertNew(task: Task, at index: Int) -> [Task] {
  // 1
  if taskExists(withTitle: task.title) {
    // 2
    let command = NSScriptCommand.current()
    command?.scriptErrorNumber = errOSACantAssign
    command?.scriptErrorString = "Task with the title '\(task.title)' already exists"
  } else {
    // 3
    if index >= tasks.count {
      tasks.append(task)
    } else {
      tasks.insert(task, at: index)
    }
 
    postNotificationOfChanges()
  }
 
  return tasks
}

这里是每条评论处所做的事:

  1. 使用现有的函数来检查是否这个名称的任务早已存在。
  2. 如果这个名称 不是 唯一的:
    • 获取对调用这个函数的脚本命令的引用。
    • 查看命令的 errorNumber errorString property; errOSACantAssign 是AppleScript的标准错误码之一。这些将被发送回调用的脚本。
  3. 如果这个名称是 唯一的:
    • 像之前一样地处理任务。
    • 发送数据变化的通知。ViewController会看到这个并更新展示。

如果app正在运行,退出,然后build并运行你的app。再次运行 4. 添加Edit Tasks 脚本。这次你应当会得到一个错误的对话框,而不是创建副本的任务。对不起,猫...

Making a mac app scriptable tutorial: Hungry Cat

5. 删除Tasks.scpt

这个脚步删除了一个任务,检查是否存在一个特定的任务,并删除它,最终删除全部完成的任务。

使用嵌入的(Nested)对象

在示例的app中,第二列展示了一个,分配给每项任务的标签的列表。到目前为止,你仍然没有办法通过脚本来使用它们 - 是时候来修复这个了!

对象的说明符可以处理对象的层次结构。这是你在这里拥有的,而应用程序拥有着任务,并且任务拥有它的标签。

Task 类一样,你需要使 Tag 脚本化。

打开 Tag.swift ,并作出下面的变化:

  • 将类的定义这行修改为: @objc(Tag) class Tag: NSObject {
  • 添加 override 关键字到 init 上。
  • 添加对象说明符的方法:
override var objectSpecifier: NSScriptObjectSpecifier {
  // 1
  guard let task = task else { return NSScriptObjectSpecifier() }
 
  // 2
  guard let taskClassDescription = task.classDescription as? NSScriptClassDescription else {
    return NSScriptObjectSpecifier()
  }
 
  // 3
  let taskSpecifier = task.objectSpecifier
 
  // 4
  let specifier = NSUniqueIDSpecifier(containerClassDescription: taskClassDescription,
    containerSpecifier: taskSpecifier, key: "tags", uniqueID: id)
  return specifier
}

上面的代码相对是比较简单的:

  1. 检查标签是否具有分配的任务。
  2. 检查任务是否具有正确的类的类描述。
  3. 获取父任务的对象说明符。
  4. 为包含在任务中的标签,构建对象说明符,并返回。

在评论 Insert tag class here 处,添加下列的代码到SDEF文件中:

<class name="tag" code="TaGg" description="A tag" inherits="item" plural="tags">
  <cocoa class="Tag"/>
  <property name="id" code="ID  " type="text" access="r"
    description="The unique identifier of the tag.">
    <cocoa key="uniqueID"/>
  </property>
  <property name="name" code="pnam" type="text" access="rw"
    description="The name of the tag.">
    <cocoa key="name"/>
  </property>
</class>

它和 Task class的数据非常相似,但一个标签只有两个暴露的property: id name

现在必须要对 Task 部分进行一下编辑,来指出它包含着tag元素了。

添加下列的代码到Task类的XML中,在 Insert element of tags here 的注释处:

<element type="tag" access="rw">
  <cocoa key="tags"/>
</element>

退出app,然后再次build并运行app。

返回 Script Editor ;如果 Scriptable Tasks dictionary 已打开,就关闭并重新打开它。观察它是否包含关于标签的信息。

如果不存在,就从 Library 中移除 Scriptable Tasks 记录并在此添加,通过将app拖拽到window中:

Making a mac app scriptable tutorial: Scriptable Tasks Dictionary 2

尝试下列脚本中的一个:

tell application "Scriptable Tasks"
  get the name of every tag of task 1
end tell

app = Application("Scriptable Tasks");
app.tasks[0].tags.name();

现在你可以在app中检索tag了 - 但添加一些新的怎么样?

你可能会注意到在 Tag.swift 中,每个 Tag 都有一个弱引用指向它自己的任务。当获取了对象说明符后,它可以帮助创建连接,因此当分配一个熄灯tag到任务中时,任务的property必须被设置。

打开 Task.swift 并添加下列的方法到 Task 的类中:

override func newScriptingObject(of objectClass: AnyClass,
                                 forValueForKey key: String,
                                 withContentsValue contentsValue: Any?,
                                 properties: [String: Any]) -> Any? {
 
  let tag: Tag = super.newScriptingObject(of: objectClass, forValueForKey: key,
                                          withContentsValue: contentsValue,
                                          properties: properties) as! Tag
  tag.task = self
 
  return tag
}

为什么你将它放到 Task 类而不是 Tag 类中,是因为这个方法被发送到了新对象的容器中。这个调用被传递到了 父类 中来获取新的标签,然后这个task的property就被赋值了。

退出,build并运行你的app。现在运行示例的脚本 6. Tasks With Tags.scpt ,它列出标签的名称,通过指定的标签列出任务,并可以删除和创建tag。

添加定制的命令

当制作一个app脚本时,你还可以采取一步:添加定制的命令。在之前的脚本中,你直接切换了任务的 completed 标记。但难道不应该更好一些 - 更安全一些么?能否可以使用一个命令来完成,而不是直接改变property?

考虑下列的脚本:

mark the first task as "done"
mark task "Feed the cat" as "not done"

我相信你已达到了SDEF文件,你完成的是正确的:这个命令必须首先被定义。

这里需要两个步骤:

  1. 告诉应用这个命令存在,和它的参数是什么。
  2. 告诉Task类它响应命令,以及要调用的方法来实现它。

在Scriptable Tasks suite中,所有的类之外,在 Insert command here 注释中,添加下列的代码:

<command name="mark" code="TaSktext">
  <direct-parameter description="One task" type="task"/>
  <parameter name="as" code="DFLG" description="'done' or 'not done'" type="text">
    <cocoa key="doneFlag"/>
  </parameter>
</command>

“等一下!“你说。“之前你说code必须是 四个 字符,但现在我有了一个八个字符的?这里发生了什么?”

当定义一个方法的时候,你提供一个两个部分的code。它组合了参数的code或类型 - 在这个case中是一个 Task 对象和一些文本。

Task 类的定义中, Insert responds-to command here 的注释处,添加下列代码:

<responds-to command="mark">
  <cocoa method="markAsDone:"/>
</responds-to>

现在返回 Task.swift 并添加下列代码:

func markAsDone(_ command: NSScriptCommand) {
  if let task = command.evaluatedReceivers as? Task,
    let doneFlag = command.evaluatedArguments?["doneFlag"] as? String {
    if self == task {
      if doneFlag == "done" {
        completed = true
      } else if doneFlag == "not done" {
        completed = false
      }
      // if doneFlag doesn't match either string, leave un-changed
    }
  }
}

markAsDone(_:) 的参数是 NSScriptCommand 类型的,它含有两个有用的property: evaluatedReceivers evaluatedArguments 。从它们这里,你会尝试获取任务和字符创的参数,并使用它们来调整相应的任务。

退出,再次build并运行你的app。在Script Editor中查看字典,如果 mark 命令未显示,请删除并重新导入它:

Making a mac app scriptable tutorial: Scriptable Tasks Dictionary 3

现在,你应该可以运行 7. Custom Command.scpt 脚本,并看到你的新的脚本正在执行中了。

注意: Swift 3改变了命令发送到对象中的方式。AppleScript仍会按照预期一般地工作,但 mark 命令在JavaScript中却失效了。我已经添加了 completed property的手动切换到JavaScript版本的 7. Custom Command.scpt 中,但也在这里留下了原来的版本。希望它可以在Swift更新之后生效。

从这儿去哪里?

你可以在 这里 下载最终版本的示例项目。

在这个mac app的可脚本化的教程中,没有覆盖到app间的通信。对于如何在app之间工作,请访问 8. Inter-App Communication.scpt 来查看一些例子。这个例子收集了今天和明天未完成的任务,并将它们插入到了一个新的TextEdit文件,样式化文本并保存文件。

对于可执行脚本app的更多信息,苹果官方的文档在这里 Scriptable Applications ,它是一个很好的开始,苹果的 Overview of Cocoa Support for Scriptable Applications 也是这样。

感兴趣于了解更多的JXA?请访问 Introduction to JavaScript for Automation Release Notes