与 Workflow 创始人的一次交流

Hey Siri!

这篇文章不是教你如何开发 Siri Extensions,毕竟那样的文章在网上没有一百篇也会有八十篇。可能大家都知道,在 iOS 12 上面应用可以创建自己的 Intents,这些 Intents 可以被 Siri 使用,也可以在“捷径”应用里面使用。

我这里要分享的其实是一个效果,以及这个效果背后的一个故事。

具体来讲,在 JSBox 支持了从应用内添加 Intents 到捷径应用,完全不需要用户去捷径里面选由 JSBox 提供的 Intents,他运行起来就像是这个样子:

基本原理

这个效果其实很容易想到如何实现,你只需要了解捷径的文件格式就行。本质上捷径也好,还是被收购之前的 Workflow 也好,他们的数据格式很简单,就是一个一个的 actions 被存储在了一个 plist 里面,你只要研究几个捷径文件,就能看到类似的存储结构:

这些结构已经被研究得很透彻,甚至有了一些相关的开源项目,比如这个 python-shortcuts 他可以通过 Python 的配置生成捷径,这个 cub-shortcuts 他可以把一个叫 Cub 的脚本语言给“编译”成捷径。

总之,你可以手动构造一个捷径,而无需通过捷径应用。所以你只需要在自己的应用里面封装好一个捷径文件,然后安装到捷径就可以了。

怎么安装?捷径支持一个 URL scheme: shortcuts://import-workflow?url=&name=,跳转到捷径就会自动安装。于是简单了,如果你的捷径比较固定,你完全可以放在一个服务器上,然后从你的应用跳转到捷径完成安装。

如果是动态生成的捷径呢(像 JSBox 那样),有两个方法:

  • 弹出 share sheet,引导用户把生成的捷径文件分享到捷径应用
  • 应用内起一个服务器,把文件挂在上面,跳到捷径使用本地服务器上的文件完成安装(JSBox 使用的方法)

我相信 JSBox 并不是第一个完成这一系列骚操作的应用,毕竟这些内容都很容易想到。

有问题

其实上面的操作在 iOS 12 以前就已经可以完成,但 iOS 12 之后加入了 Custom Intents,这件事情变得困难了。难就难在,Custom Intents 被封装到那个 Plist 的时候(Intents 会变成捷径的一个 action),你不知道他是如何序列化的。

在没有 Custom Intents 的时候,每个 action 不过就是个类似字典的东西,但现在有一整块的二进制数据不知道怎么来的。

一个最容易想到的方案是 NSKeyedArchiver,当然不管用,NSPropertyListSerialization 直接去序列化那个对象也不管用,但我注意到由捷径应用生成出来的那个文件会比我生成出来的要小,于是我开始分析他生成出来的一些字节,希望能找到一些线索。

最后我并没有找到什么有用的线索,但是还是完成了这个需求。

我是怎么做的

很简单,我用捷径应用生成了一个捷径文件,然后把 Intents 那块字节给剥离了出来。当然了,我最终要生成的文件里面,有些内容是动态的,比如标题和 id,这个时候我开始逐个字节的“组装”我最后要的二进制文件。

于是我的代码里面出现了这样一些乱七八糟的东西:

```objc
char bytes1[] = { 0x0A };
[intentData appendData:[NSData dataWithBytes:bytes1 length:1]];

char bytes2[] = { 0x00 + byteOffset };
[intentData appendData:[NSData dataWithBytes:bytes2 length:1]];

[intentData appendData:keyData];

top Created with Sketch.