關於SiriKit
在6月14日凌晨的WWDC2016
大會上,蘋果提出iOS10
是一次里程碑並且推出了十個新特性,大部分的特性是基於iPhone
自身的原生應用的更新,具體的特性筆者不在這裡再次敘述,請看客們移步WWDC2016下載自行觀賞。要說里程碑在筆者看來有些誇大其實了,不過新增的通知中心聯動3D Touch
確實為人機互動帶來新的發展,另外一個最大的亮點在於Siri
的介面開放。在iOS10
中提供了SiriKit
框架在使用者使用Siri
的時候會生成INExtension
物件來告知我們的應用,通過實現方法來讓Siri
獲取應用想要展示給使用者的內容
在iOS10
之後,蘋果希望Siri
能夠給使用者帶來更多的功能體驗,基於這個出發點,新增了SiriKit
框架。Siri
通過語言處理系統對使用者發出的對話請求進行解析之後生成一個用來描述對話內容的Intents
事件,然後通過SiriKit
框架分發給整合框架的應用程式以此來獲取應用的內容,比如完成類似通過文字匹配查詢應用聊天記錄、聊天物件
的功能,此外它還支援為使用者使用蘋果地圖時提供應用內建服務
等功能。通過官方文件我們可以看到SiriKit
框架支援的六類服務分別是:
- 語音和視訊通話
- 傳送訊息
- 收款或者付款
- 圖片搜尋
- 管理鍛鍊
- 行程預約
Siri
和Maps
通過Intents extension
的擴充套件方式和我們的應用進行互動,其中,型別為INExtension
的物件扮演著Intents extension
擴充套件中直接協同Siri
物件共同響應使用者請求的關鍵角色。當我們實現了Intents extension
擴充套件併產生了一個Siri
請求事件時,一個典型的Intent
事件的處理過程中總共有這三個步驟Resolve
、Confirm
和Handle
:
Resolve
階段。在Siri
獲取到使用者的語音輸入之後,生成一個INIntent
物件,將語音中的關鍵資訊提取出來並且填充對應的屬性。這個物件在稍後會傳遞給我們設定好的INExtension
子類物件進行處理,根據子類遵循的不同服務protocol
來選擇不同的解決方案Confirm
階段。在上一個階段通過handler(for intent:)
返回了處理intent
的物件,此階段會依次呼叫confirm
打頭的例項方法來判斷Siri
填充的資訊是否完成。匹配的判斷結果包括Exactly one match
、Two or more matches
以及No match
三種情況。這個過程中可以讓Siri
向使用者徵求更具體的引數資訊- 在
confirm
方法執行完成之後,Siri
進行最後的處理階段,生成答覆物件,並且向此intent
物件確認處理結果然後執顯示結果給使用者看
建立Intents Extension
SiriKit
通過新增App Extension
的方式來完成整合,這是一種獨立於應用本身執行的程式碼結構,作為應用的擴充套件功能,只有在需要的時候系統會喚醒這些Extension
程式碼來執行任務,然後在執行完畢之後將其殺死。另一方面,這些Extension
在執行過程中的可佔用記憶體是較少的,並且由於呼叫時機的限制,我們也無法在執行期間做一些壞事
現階段整合SiriKit
的條件是需要將開發工具升級到Xcode8
,需要使用開發者賬號到官方網站去下載Xcode8_beta
版,並且需要將一臺測試裝置升級到iOS10
系統。選中我們的應用,進入專案總覽介面,新增一個TARGET
如上圖所示,我建立的Intents Extension
被我命名為LXDSiriExtension
。記住在建立好一個Extension
的時候,會詢問你是否啟用這個擴充套件,勾選是。另外還會提示你是否連同Intents UI Extension
一併建立了,我們同樣選是。這樣我們在專案下面總共建立了LXDSiriExtension
和LXDSiriExtensionUI
兩個TARGET
,這兩個檔案目錄下面分別存在著一個新的info.plist
檔案,這個檔案用來設定intent
事件發生時我們設定的處理類。這裡借用WWDC
在講解時的一張ppt
來了解:
按圖中的層次展開,IntentsSupported
和IntentsRestrictedWhileLocked
分別是兩個字串陣列,每一個字串表示的是應用擴充套件處理的intent
事件的類名。前者表示支援的事件型別,後者表示在非鎖屏狀態下執行的事件型別。檔案預設是workout
型別的事件,在這裡筆者改成了傳送訊息INSendMessageIntent
。除此之外,NSExtensionPrincipalClass
對應的是INExtension
子類類名,這個類用來獲取處理intent
事件的類。
另外,官方講解中提到了Embedded frameworks
,在session
中蘋果開發人員通過一個訊息聊天應用來示例整合SiriKit
。由於應用擴充套件自身的執行機制和應用本身的執行機制不同,彼此之間建立的類是不能訪問使用的。因此把我們需要的類開發成frameworks
的方式匯入我們的應用之後就能夠在兩種之中都使用到這些類。本文未使用frameworks
匯入功能,而是模擬了一個類用來管理事件處理過程中的部分邏輯,但是Embedded frameworks
這個使用的準則需要記住。這個模擬類的具體程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import Intents class LXDMatch { var handle: String? var displayName: String? var contactIdentifier: String? convenience init(handle: String, _ displayName: String, _ contactIdentifier: String) { self.init() self.handle = handle self.displayName = displayName self.contactIdentifier = contactIdentifier } func inPerson() -> INPerson { return INPerson(handle: handle!, displayName: displayName, contactIdentifier: contactIdentifier) } } class LXDAccount { private static let instance = LXDAccount() private init() { print("only call share() to get an instance of LXDAccount") } class func share() -> LXDAccount { return LXDAccount.instance } func contact(matchingName: String) -> [LXDMatch] { return [LXDMatch(handle: NSStringFromClass(LXDSendMessageIntentHandler.classForCoder()), matchingName, matchingName)] } func send(message: String, to recipients: [INPerson]) -> INSendMessageIntentResponseCode { print("Send a message: \"\(message)\" to \(recipients)") return .success } } |
在完成這些需要的工作之後,我們還需要對應用本身的Info.plist
配置檔案進行設定,新增一個關鍵字為NSSiriUsageDescription
的字串物件,對應填寫的字串將在我們徵詢使用者Siri
許可權的時候顯示給使用者看。比如Siri想要訪問您的應用資訊
之類的提示語。然後通過INPreferences
類方法向使用者請求Siri
訪問許可權
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import Intents INPreferences.requestSiriAuthorization { switch $0 { case .authorized: print("使用者已授權") break case .notDetermined: print("未決定") break case .restricted: print("許可權受限制") break case .denied: print("拒絕授權") break } } |
程式碼實現
首先我們需要一個INExtension
的子類,你也可以在預設建立的子類中實現程式碼。在方法中,我們通過判斷intent
的型別來建立對應的處理者例項,然後返回。在本文的示例中,假設我們對Siri
說出這麼一句話 Siri,在微信上告訴我的家人們今天我不回去吃飯了
:
1 2 3 4 5 6 7 8 9 10 |
class LXDIntentHandler: INExtension { override func handler(for intent: INIntent) -> AnyObject? { if intent is INSendMessageIntent { return LXDSendMessageIntentHandler() } // 這裡可以判斷更多型別來返回 return nil } } |
通過判斷intent
事件是傳送訊息的聊天事件後,筆者建立了一個用來處理事件的LXDSendMessageIntentHandler
類物件,並且返回。在物件建立完成之後需要完成Resolve
、Confirm
和Handle
三個步驟,具體操作需要子類遵循實現INSendMessageIntentHandling
協議來完成:
- Resolve階段
這個階段需要我們找到訊息的具體接收者。在這個過程中,可能會出現三種情況:Exactly one match
、Two or more matches
以及No matches
,對於這三種情況的處理分別如下:
123456789101112131415161718192021222324252627282930func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: ([INPersonResolutionResult]) -> Void) {if let recipients = intent.recipients {var resolutionResults = [INPersonResolutionResult]()for recipient in recipients {let matches = LXDAccount.share().contact(matchingName: recipient.displayName)switch matches.count {case 2...Int.max: //兩個或更多匹配結果let disambiguations = matches.map { $0.inPerson() }resolutionResults.append(INPersonResolutionResult.disambiguation(with: disambiguations))breakcase 1: //一個匹配結果let recipient = matches[0].inPerson()resolutionResults.append(INPersonResolutionResult.success(with: recipient))breakcase 0: //無匹配結果resolutionResults.append(INPersonResolutionResult.unsupported(with: .none))breakdefault:break}}completion(resolutionResults)} else {//未從使用者語音中提取到資訊,需要向使用者徵詢更多關鍵資訊completion([INPersonResolutionResult.needsValue()])}}
上面的程式碼用來確認出訊息中的我的家人們
指代的是哪些人,其中每個聯絡人最終用一個INPerson
的物件來表示。接著我們需要匹配訊息的內容:
12345678func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: (INStringResolutionResult) -> Void) {if let text = intent.content where !text.isEmpty {completion(INStringResolutionResult.success(with: text))} else {//向使用者徵詢傳送的訊息內容completion(INStringResolutionResult.needsValue())}}
在匹配完訊息接收者跟訊息內容之後,對於intent
事件的處理就會進入第二階段Confirm
確認值是否正確 - Confirm階段
在這個階段intent
物件本身的資訊預計是已經完成填充的,我們通過獲取這些填充值來判斷是否符合我們的要求。同時在這個階段,Siri
會嘗試喚醒應用來準備完成最後的處理操作。前面說了為了保證在應用和應用擴充之間能夠進行通訊,最好使用frameworks
的方式來標記應用是否被啟動,再進行相應操作。
12345678910func confirm(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {/// let content = intent.content/// let recipients = intent.recipients/// do or judge in content & recipientscompletion(INSendMessageIntentResponse(code: .success, userActivity: nil))/// Launch your app to do something like store message record/// Use a singleton in frameworks to remark if the app has launched/// if not launched, use the code following/// completion(INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: nil))}
Confirm
階段是我們最後可以嘗試修改intent
事件中傳遞的數值的時候。要記住一點,完全精確的內容固然是最好的答案,但是過多的讓Siri
詢問使用者引數的詳細資訊也會導致使用者的牴觸 - Handle階段
Handle
階段不需要做太多額外的工作,判斷一下訊息接收者或訊息內容是否存在,如果存在,執行類似儲存/傳送
的工作,然後完成。否則告訴Siri
本次的intent
事件處理處理失敗,我們還可以通過配置NSUserActivity
物件來告訴Siri
失敗的原因
1234567891011func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {if intent.recipients != nil && intent.content != nil {/// do some thing success send messagelet success = LXDAccount.share().send(message: intent.content!, to: intent.recipients!)completion(INSendMessageIntentResponse(code: success, userActivity: nil))} else {let userActivity = NSUserActivity(activityType: String(INSendMessageIntent))userActivity.userInfo = [NSString(string: "error") : String("AppDidNotLaunch")]completion(INSendMessageIntentResponse(code: .failure, userActivity: userActivity))}}
事件UI
可以看到上面的程式碼主要集中在事件處理的邏輯上,那麼在和Siri
互動的過程中,我們同樣可以讓Siri
展示響應的自定義介面:
在我們建立Intents Extension
的時候,同時Xcode
也詢問我們是否建立Intents UI Extension
。在後者的檔案目錄下也有一個Info.plist
,有著跟前面類似的鍵值對,差別在於後者只有一個狀態的設定。
在這個檔案目錄下存在一個故事板MainInterface
,這個故事板就是Siri
和應用互動時展示給使用者看的介面。通過修改這個故事板的介面元素,就可以實現上圖中的效果了。此外,在這個介面將要展示之前,我們可以修改類檔案中的程式碼完成介面資訊填充的操作:
1 2 3 4 5 6 7 8 9 10 |
func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) { //這裡執行介面設定的程式碼,完成之後執行completion程式碼就會讓介面展示出來 if let completion = completion { completion(self.desiredSize) } } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize } |
尾言
在觀看WWDC2016
的新特性的時候,最開始給Siri
和應用的互動驚豔到了。但是後來閱讀文件發現這種互動仍然存在著過多的限制,整體而言並沒有對Siri
的使用帶來更明顯的提升。但是毫無疑問,這種互動如果蘋果能繼續對其進行補充發展,可以給我們的應用帶來更多的新活力。