與 Workflow 創始人的一次交流 [小專欄文章轉移]
本文轉移自本人小專欄的老文: 與 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,這個時候我開始逐個位元組的“組裝”我最後要的二進位制檔案。
於是我的程式碼裡面出現了這樣一些亂七八糟的東西:
char bytes1[] = { 0x0A }; [intentData appendData:[NSData dataWithBytes:bytes1 length:1]]; char bytes2[] = { 0x00 + byteOffset }; [intentData appendData:[NSData dataWithBytes:bytes2 length:1]]; [intentData appendData:keyData]; char bytes3[] = { 0xFA, 0xFF, 0x1F }; [intentData appendData:[NSData dataWithBytes:bytes3 length:3]]; char bytes4[] = { 0xD0 + byteOffset }; [intentData appendData:[NSData dataWithBytes:bytes4 length:1]]; [intentData appendData:[NSData dataWithContentsOfFile:[sourceFolder stringByAppendingPathComponent:@"intent-template"]]]; char bytes5[] = { 0x07 + byteOffset }; [intentData appendData:[NSData dataWithBytes:bytes5 length:1]]; char bytes6[] = { 0xE8, 0xBF, 0x90, 0xE8, 0xA1, 0x8C, 0x20 }; [intentData appendData:[NSData dataWithBytes:bytes6 length:7]];
這些 Magic Numbers 只是我個人狀況,你要實現的話需要自己分析具體的二進位制檔案,最終我還是按老方法封裝一個 Plist 並用 NSPropertyListSerialization
輸出成一個檔案。
就得到了和捷徑應用生成出來的一模一樣的內容,也就實現了文章開頭那個安裝動作。
PS: 請注意我這篇文章基於幾個月前的實驗,現在可能已經發生變化,有可能有更簡單的方案了。
與 Workflow 創始人的交流
我把這個效果發到Twitter 上的時候,引起了 Workflow 創始人Ari Weinstein 的注意,他私信了我說做的很酷,關注了我。Workflow 團隊被收購之後,Ari Weinstein 就在 Apple 負責捷徑應用。
他在私信裡面還建議了我措辭方面的問題,他說:
- For adding to Siri, please use “Add to Siri” rather than “Add to Siri Shortcuts”
- “Add to Shortcuts App” is fine since we don’t have official guidance there
也就是說不能用 “Add to Siri Shortcuts” 而必須用 “Add to Siri”,但 “Add to Shortcuts App” 並沒有要求,為什麼?因為本篇文章講的根本就不是文件裡面有的東西,也就沒有什麼規範可言了。
被大佬注意到並不容易,藉此機會我跟他問了一下關於那塊二進位制檔案的問題,他也像我以為的那樣只要用 NSKeyedArchiver 序列化一下就可以了。當然不行,我告訴他那樣做會直接讓捷徑崩潰掉,他表示很奇怪。
不過... 最精彩的地方來了,過了一會兒他告訴了我一個私有的 C 函式,讓我試試。
// 考慮到這是個私有介面,我打碼了 extern INIntent *INIntentWithXXXXXXXXXX(__kindof INIntent *xxxxx);
我一試果然沒問題,和我用位元組操作生成出來的檔案一模一樣!和他的交流讓我很高興,不過我最終並沒有用他的方案。
因為我用 Public APIs 已經可以做出這個效果了,就沒必要增加一些動態呼叫來讓稽核變得更有風險了,不過這仍然是一次很有意思的對話。
最後
Workflow 的確是一款很偉大的產品,被 Apple 收購之後變得更好,我在各種場合都表達過對他的喜愛。這個創始團隊僅有幾個人,就能做出一個如此強大的產品,實在是令人佩服。
一個程式設計師,哪怕是對捷徑毫無瞭解,經過短暫的使用之後也一定會認同我的看法。
再次對捷徑團隊表示敬意。