SiriKit官方文档 - Part1-3 解决和处理意图

最近在看SiriKit的官方文档,自啃 + Google翻译,如有错误或语义不当还望指出。

官方文档原文 - Resolving and Handling Intents

你的Intents扩展的入口是INExtension对象,INExtension对象的作用是引导Siri找到能够处理用户传入意图的对象。

在你的扩展中,你经常使用以下三种类型的对象来处理意图:

  1. 一种意图对象,其包含Siri从用户收集的数据。
  2. 一种处理对象,其实用于解析、确认和处理意图的自定义对象。
  3. 一种响应对象,其包含对意图的响应。

当SiriKit有一个意图对象需要你的App处理时,它会要求你的INExtension对象提供一个能够处理意图的处理对象。处理对象对他们处理的意图采用特定协议。处理协议都是相同的结构(具有解析、确认和处理特定意图的方法)。

当实现处理程序(handler)时,你必须实现用于处理意图的方法。虽然所有其他方法是可选的,但建议实现其中一些。在你尝试处理一个意图之前,请使用解析和确认意图的方法来验证此意图的内容。在验证过程中,Siri会根据需要与用户进行交互,以便收集你所需的附加信息。

图3-1展示了SiriKit与一个处理对象之间的高阶流程。当用户发起与你的App相关的请求时,SiriKit会创建一个相应的意图对象。如果意图有需要解析的参数,SiriKit需要你的处理程序(handler)单独解析这些参数。当所有参数被解决时,SiriKit向你确认你是否准备好处理该意图。如果你表明你已经准备好了,SiriKit会让你去处理这个意图。此时,你执行由意图表示的任务。

图3-1 处理打车请求意图

有关用于实现意图、响应和处理对象的类和协议的列表,参阅意图域

向处理对象发送意图

当一个意图到了你的App,SiriKit需要你的Intents扩展为该意图提供一个处理对象。说具体点就是SiriKit调用你的INExtension子类的handlerForIntent:方法,这是你的扩展的入口。你从此方法返回一个SiriKit用于解析、确认和处理意图的对象。

代码3-1展示了一个handlerForIntent:方法的实现,该方法为消息和VoIP电话域中的Intents提供了处理对象。不管你处理哪个意图这个方法的结构都是相同的。你检查传入意图的类,并返回一个能够处理该意图的处理对象。你可以用一个单一的处理对象来处理你所有支持的意图,或者为每个意图创建不同的处理对象。在该示例中,Intents扩展为所有与消息相关的意图返回相同的处理对象,并为音频和视频电话意图返回不同的处理对象。

代码3-1返回意图的处理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (nullable id) handlerForIntent:(INIntent*)intent {
id handler = nil;

if ([intent isKindOfClass:[INSendMessageIntent class]] ||
[intent isKindOfClass:[INSearchForMessagesIntent class]] ||
[intent isKindOfClass:[INSetMessageAttributeIntent class]]) {
handler = [[MyMessageHandler alloc] init];
}
else if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
handler = [[MyAudioCallHandler alloc] init];
}
else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
handler = [[MyAudioCallHandler alloc] init];
}

return handler;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override func handler(for intent: INIntent) -> Any? {
var handler = nil

if intent is INSendMessageIntent ||
intent is INSearchForMessagesIntent ||
intent is INSetMessageAttributeIntent {
handler = MyMessageHandler()
}
else if intent is INStartAudioCallIntent {
handler = MyAudioCallHandler()
}
else if intent is INStartVideoCallIntent {
handler = MyVideoCallHandler()
}

return handler
}

因为你的Intents扩展是按需启动的,你的INExtension子类和处理对象应该尽可能少地保持状态信息。 SiriKit通常需要你在处理单个意图的期间多次提供一个处理对象。可能需要一些时间来解析意图的所有参数,尤其是如果Siri必须需要用户提供清晰的参数或附加信息的时候。每次请求一个新的处理对象能保证对象总是有效的。这也意味着你的处理方法不应该依赖于缓存的状态信息。相反,他们应该始终对任何所需的数据执行新请求。

有关如何实现此方法的信息,参阅INIntentHandlerProviding Protocol Reference

解析意图参数

解析阶段是你验证意图的参数的好时机,你也可以确保你有继续下一步需要的信息。由于源于Siri的请求是语音命令,用户所提供的初始信息可能是不完整或不明确的。在解析阶段,你可以检查每个参数并告诉Siri它所提供的值是否符合,或者是否需要Siri进一步询问用户的一些问题。

大多数意图会提供至少有一个参数给你的处理对象来解析。当实现你的处理程序时,建议你实现给定意图的所有解析方法。即使你不使用给定的参数,也可以使用相应的解析方法告诉SiriKit你不使用这个参数。告诉SiriKit你不使用参数可以阻止它向用户询问关于参数的任何问题。

你编写的每个解析方法的结构应该是相同的:

  1. 从意图对象获取参数值。
  2. 验证参数值,并将其与你的App使用的数据进行匹配。
  3. 实例化相应能解析意图的INIntentResolutionResult子类。
  4. 使用你的解析结果对象执行提供的成功回调block。

Intents框架为每个可以用作参数的数据类型定义了INIntentResolutionResult子类。每个子类包含类方法,用于指定是成功解析数据还是需要更多信息。父类INIntentResolutionResult还包含用于常用解析结果的方法。表3-1列出了可能的解析以及何时你可能使用。

表3-1解析意图参数时的可能结果

解析类型 样例
你已成功匹配参数值 如果可以使用提供的参数值来处理意图。
你应该尽力获得的参数进行匹配解析,即使这意味着有根据的猜测用户的意思。
例如,如果用户为某个人指定了名称“John”,且该名称只有一个联系人,则可以将该参数成功解析为该联系人。
有关用于成功匹配参数值的方法的名称,参阅相应解析方案结果对象的文档。
不需要参数值 当不需要参数的值来处理意图时。
例如,当意图包含打开锻炼的目标(通常没有目标)时,你可能返回此解析类型。
使用notRequired类方法创建你的解析结果对象。
需要消除歧义参数值 当你识别两个或更多可能的参数值,并且不能确定地选择一个参数值时。
此结果可能导致与用户的额外交互,以选择所提供的值之一。
有关用于消除结果歧义的方法的名称,参阅相应解析方案结果对象的文档。
该值需要用户确认 当你希望用户确认你选择的值时。
在你识别到一个参数值,但是你不确定这个值是否是正确的值时使用该结果
例如,当你选择的值与用户提供的值不同时,你可能会使用此结果,但你认为你选择的值是用户想要的值。
Siri总会需要用户确认某些类型的意图的值,比如涉及金融交易的意图。
有关需要用户确认的方法的名称,参阅相应解析方案结果对象的文档。
需要一个值 当缺少你需要的参数的值时。
例如,如果用户向其他人请求付款,但未指定付款金额,可以返回此结果。
使用needsValue类方法创建你的解析结果对象。
不支持该值 如果你的应用不支持某个特定值,或该值与其他参数的值冲突,可以指定此解析。
例如,当用户想要以瑞士法郎进行付款时,但你的App需要以欧元或美元进行交易,你可能会返回此解析。
使用unsupported类方法来创建解析结果对象。

在解析参数时,尽可能快地达到成功的解析。请求更多信息会导致额外的用户交互和处理程序额外的调用,这会导致延迟,也可能使用户感到不适。建议尝试基于用户的固有模式和习惯选择合理的值,并且仅在需要时要求消除歧义或确认。

代码3-2展示了一个乘车预订App用于验证乘车的下车位置的示例。如果用户提供了一个下车位置且其位于App的服务区内,则该方法返回成功的结果。如果值在App的服务区域之外,该方法可以让SiriKit知道该值不受支持。如果未提供值,则方法会要求用户提供值。

代码3-2解析意图的必需参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)resolveDropOffLocationForRequestRide:(INRequestRideIntent *)intent
withCompletion:(void (^)(INPlacemarkResolutionResult *resolutionResult))completion {
CLPlacemark* location = intent.dropOffLocation;
INPlacemarkResolutionResult* result = nil;

if (location) {
if ([self locationIsInsideServiceArea:location])
result = [INPlacemarkResolutionResult successWithResolvedPlacemark:location];
else
result = [INPlacemarkResolutionResult unsupported];
}
else {
result = [INPlacemarkResolutionResult needsValue];
}

completion(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func resolveDropOffLocation(forRequestRide intent: INRequestRideIntent,
with completion: (INPlacemarkResolutionResult) -> Void) {
let location = intent.dropOffLocation
var result : INPlacemarkResolutionResult = nil

if location != nil {
if self.locationIsInsideServiceArea(location) {
result = INPlacemarkResolutionResult.successWithResolvedValue(location)
}
else {
result = INPlacemarkResolutionResult.unsupported()
}
}
else {
result = INPlacemarkResolutionResult.needsValue()
}

completion(result)
}

确认请求

使用意图的确认(confirm)阶段来执行任何意图参数的最终验证,并验证你是否已准备好处理意图。SiriKit需要你在所有参数都解析符合后才能确认意图。你可以使用确认方法执行一次最终检查。如果你的应用程序依赖于基于网络的服务,你还可以使用确认方法验证这些服务是否可用。虽然确认方法不是必须实现的,但是强烈建议去实现。

在确认方法中,执行任何确认任务,然后用你在处理意图时需要使用的数据去创建响应对象。如果提示用户确认,Siri将在确认界面中显示响应对象的详细信息。Siri总是提示用户确认重要的请求,特别是那些不可撤销或涉及金融交易的请求。在其他情况下可能不会提示用户。

代码3-3展示了一个简单确认开始锻炼方法。此方法提供一个响应对象表示App已准备好开始锻炼。此方法会将用户活动对象置为nil,这将导致SiriKit创建默认用户活动对象。

代码3-3确认锻炼的开始

1
2
3
4
5
6
7
8
9
- (void)confirmStartWorkout:(INStartWorkoutIntent *)startWorkoutIntent
completion:(void (^)(INStartWorkoutIntentResponse * _Nonnull))completion {
// 确认App已经准备好了

// 提供响应
INStartWorkoutIntentResponse* response = [[INStartWorkoutIntentResponse alloc] initWithCode:INStartWorkoutIntentResponseCodeReady userActivity:nil];

completion(response);
}
1
2
3
4
5
6
7
8
func confirmStartWorkout(startWorkout intent: INStartWorkoutIntent,
completion: (INStartWorkoutIntentResponse) -> Void) {
// 确认App已经准备好了

// 提供响应
let response = INStartWorkoutIntentResponse(code: .ready, userActivity: nil)
completion(response)
}

处理请求

处理意图的最后阶段是执行与该意图相关联的任务。你的处理对象的处理方法有两个职责:

  1. 执行与意图关联的任务。
  2. 返回一个响应对象,其中包含你的App执行操作的详细信息。

你如何处理意图取决于意图本身。如果可以的话,直接从你的Intents扩展处理一个意图。然而,一些意图需要回到你的App进行一些流程操作。例如,成功处理开始锻炼意图需要启动你的App以开始锻炼。对于其他意图,则执行任务所需的任何步骤。执行任务后,将你执行操作的详细信息放入返回到SiriKit的响应对象中。

代码3-4展示了处理开始新锻炼的方法。在这种情况下,处理程序会告诉Siri启动相关App来开始锻炼。其他意图可能需要使用响应对象提供附加数据。

代码3-4处理开始锻炼

1
2
3
4
5
6
- (void)handleStartWorkout:(INStartWorkoutIntent *)startWorkoutIntent
completion:(void (^) (INStartWorkoutIntentResponse * _Nonnull))completion {
INStartWorkoutIntentResponse* response = [[INStartWorkoutIntentResponse alloc] initWithCode:INStartWorkoutIntentResponseCodeContinueInApp userActivity:nil];

completion(response);
}
1
2
3
4
5
6
func handleStartWorkout(startWorkout intent: INStartWorkoutIntent,
completion: (INStartWorkoutIntentResponse) -> Void) {
let response = INStartWorkoutIntentResponse(code: .continueInApp, userActivity: nil)

completion(response)
}

每个响应对象都支持一个可选的NSUserActivity对象,其中包含有关如何启动App的详细信息。如果不指定此对象,SiriKit会为你创建一个对象,并插入一个包含意图和你的响应的INInteraction对象。如果要提供其他信息,可以自己创建NSUserActivity对象,并向其userInfo字典中添加信息。

处理意图时所做的更改也应该反映在你的iOS应用界面中。作为执行任务的一部分,你的Intents扩展应将任何相关信息回传到其父iOS应用中。无论你在响应中提供NSUserActivity对象与否都要这样做。如果Intents扩展成功处理了意图,则可能永远不会传递用户活动对象。为确保更改信息被送达,请更新任何共享数据结构并可使用后台应用刷新、静默推送通知或其他技术将更改传回到iOS应用。

处理意图的建议

在确认或处理方法中提供响应对象时,请考虑以下事项:

  1. 在响应对象中包含尽可能多的信息。 Siri和Maps在确认或处理意图时向用户传达尽可能多的信息。使用详细的信息填充响应对象能清楚地传达你的App所做的事情,以创建更好的用户体验。
  2. 将数据对象分配给响应对象之前,完全配置数据对象。 对于大多数响应对象,属性使用复制语义。如果你在属性中获取对象并对其进行修改,那么你所做的更改将在副本上进行而不是原始副本。因此,在进行更改后你必须再设置属性的值。
  3. 尽快返回你的响应对象。 由于Siri和Maps在活跃地与用户进行沟通,因此请尽可能快地返回响应。如果返回响应需要超过几秒钟,请使用“正在进行”代码返回你的响应,以指示你仍在处理请求。

适当的时候SiriKit会利用用户联系人数据库中的信息填写参数。如果你的App被拒绝访问用户的联系人,那么与这些联系人相关的信息可能不会包含在意图中。例如,意图中的任何INPerson对象可能只包含其spokenPhrase属性中的值,并且不包含任何其他联系信息。这种缺乏访问权限还会阻止SiriKit访问联系人的地址,这导致一个短语,例如“我需要使用乘车”导致意图没有下车位置。如果你的App被拒绝访问联系人,建议你提示用户允许访问联系人以获取更好的用户体验。

SiriKit译文系列

SiriKit官方文档 - Part1-1 SiriKit介绍
SiriKit官方文档 - Part1-2 创建一个Intents扩展
SiriKit官方文档 - Part1-3 解决和处理意图
SiriKit官方文档 - Part1-4 指定自定义词汇
SiriKit官方文档 - Part1-5 创建Intents UI扩展
SiriKit官方文档 - Part2-1 参考-意图域
SiriKit官方文档 - Part2-2 参考-App词汇文件格式