未调用自定义 Intent 处理程序 - iOS 声称 IntentHandlerMethodForIntent 未实现

Custom Intent Handler Not Called - iOS claims IntentHandlerMethodForIntent not Implemented

我的 objective-C 自定义 Intent 实现中的 IntentHandler 无法接收来自语音激活快捷方式的调用。当使用 Siri 调用捐赠的交互时,我发现我在控制台应用程序中收到以下错误,声称未实现意图的意图处理程序方法:

-[INIntentDeliverer _invokeIntentHandlerMethodForIntent:intentHandler:parameterNamed:keyForSelectors:executionHandler:unimplementedHandler:] _invokeIntentHandlerMethodForIntent sirikit.intent.voice_commands.RunVoiceCommandIntent


-[WFRVCIntentHandler stateMachineForIntent:] Created state machine <WFRVCStateMachine: 0x102e23970 state=WaitingForServer phase=Unknown> for intent with identifier 8A87FC68-329D-49FF-B534-B0A5821854CA


-[INIntentDeliverer _invokeIntentHandlerMethodForIntent:intentHandler:parameterNamed:keyForSelectors:executionHandler:unimplementedHandler:] _invokeIntentHandlerMethodForIntent sirikit.intent.voice_commands.RunVoiceCommandIntent

此错误与以下事实一致:尝试使用语音命令触发自定义意图会导致 iOS 调用我的 appDelegate,尤其是 application:continueUserActivity:restorationHandler:。根据文档,restorationHandler 仅应在未处理意图且必须由主应用程序处理时调用。

由于 objective-C 实现的文档很少,我不知道我遗漏了什么。我试图将 Siri 快捷方式的示例 SoupChef 应用程序实现映射到我的实现。我无法弄清楚我哪里出错了。这是我的实现(抱歉所有细节,但我希望你能看到错误):

首先,我实现了另外两个目标;共享框架和意图扩展。我还实现了一个意图定义文件。

这是我的目标图片:

W_P_r 是主应用程序,W_P_rKit 是共享框架,PartsListManagerIntents 是 Intents 扩展。

接下来,这是我的意图定义文件及其所属的目标成员资格:

我还在主要目标和 PartsListIntentManager 目标的功能部分添加了一个应用程序组。我还为主要目标添加了 Siri 功能。

所有这些都会自动创建一些代码,包括 PartsListManagerIntents 目标中的默认 IntentHandler.m 和 info.plist。我已将 info.plist 更新如下:

这里是自动生成的 IntentHandler(我已对其进行修改以记录 activity 并调用驻留在 W_P_rKit 共享框架中的特定意图处理程序:

#import "IntentHandler.h"
#import <Intents/Intents.h>
#import <W_P_rKit/W_P_rKit.h>
#import "CreatePartsListIntentHandler.h"
#import "P__tHandler.h"
#import <os/log.h>

@interface IntentHandler () /* <CreatePartsListIntentHandling, P__tHandling> */

@end


@implementation IntentHandler

- (id)handlerForIntent:(INIntent *)intent {

    os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "handlerForIntent: Reached IntentHandler.");

    if ([intent.identifier isEqualToString:@"P__rIntent"]) {
        NSLog(@"P__rIntent");
        return [[P__rIntentHandler alloc] init];
    }
    else if ([intent.identifier isEqualToString:@"CreatePartsListIntent"]) {
        NSLog(@"CreatePartsListIntent");
        os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "handlerForIntent: IntentHandler Received CreatePartsListIntent.");
        return [[CreatePartsListIntentHandler alloc] init];
    
    }
    return self;
}

请注意,CreatePartsListIntentHandler 是一个 class,它实现了 CreatePartsListIntentHandling 协议(解析、确认和处理 IntentHandler 的方法)。

下面是应该触发 iOS 调用 IntentHandler 的相关实现:

在我的应用程序中,当用户填写新项目的名称时,我调用捐赠交互如下:

CreatePartsListIntent *data = [[CreatePartsListIntent alloc] init];
data.projectName = [projectPlistName copy];
data.quantity = [NSNumber numberWithInteger : currentProjectQuantity];
[[W_P_rDonationManager sharedInstance] donateCreatePartsListIntent : data];

对 donateCreatePartsListIntent 的调用执行以下操作:

data.suggestedInvocationPhrase = @"Create Parts List";
INInteraction* interaction = [[INInteraction alloc] initWithIntent:data response:nil];
[interaction donateInteractionWithCompletion:^(NSError * _Nullable error) { ... }

一旦用户创建了空的零件列表(强制发生上述交互捐赠),视图控制器 将显示一个“添加 Siri 快捷方式”按钮。自动点击按钮 调用以下方法创建快捷方式:

-(void) addCreatePartsListShortcutWasTapped {
    CreatePartsListIntent *intentWithData = [[WoodPickerDonationManager sharedInstance] prepareCreatePartsListIntent :
                                         @"" withNumberOfAssemblies : 1];
    INShortcut *shortcut = [[INShortcut alloc] initWithIntent:intentWithData];
    INUIAddVoiceShortcutViewController *addSiri = [[INUIAddVoiceShortcutViewController alloc] initWithShortcut:shortcut];
    addSiri.delegate = self;    
    [self presentViewController:addSiri animated:YES completion: nil];
}

对 prepareCreatePartsListIntent 的调用执行以下操作:

-(CreatePartsListIntent *) prepareCreatePartsListIntent : (NSString *) partsListName withNumberOfAssemblies : (NSInteger) quantity {

    CreatePartsListIntent *intentWithData = [[CreatePartsListIntent alloc] init];

    intentWithData.projectName = partsListName;
    intentWithData.quantity = [NSNumber numberWithInteger: quantity];
    intentWithData.suggestedInvocationPhrase = @"Create Parts List";

    return intentWithData;
}

这确实创建了一个在快捷方式应用程序中可见的快捷方式。单击快捷方式或说出调用短语将带您 直接到应用程序委托的 application:userActivity:restorationHandler。但它不调用 IntentHandler。

为什么我的 IntentHandler 没有被调用?为什么 iOS 发送错误消息 _invokeIntentHandlerMethodForIntent:intentHandler:parameterNamed:keyForSelectors:executionHandler:unimplementedHandler:?

实际上我已经为此苦苦挣扎了好几个星期。任何帮助或提示都会很有帮助。

我怀疑您的 .intentdefinition 文件的 Target Membership 选择过于严格。我相信您应该为所有三个目标将其设置为“Public Intent 类”(屏幕截图显示其中两个目标为“No Generated 类”。

我不确定这是问题的原因,但值得尝试查看问题或控制台日志消息是否消失。

我会注意到,在混合 Objective C / Swift 项目中,我遇到了与您相同的症状。 AppDelegate 在 Objective C 中,大多数其他代码在 Swift 中。我还看到 AppDelegate 方法被调用,但意图处理程序没有。尽管就我而言,我将所有目标都设置为“Public Intent 类”,并且控制台不会显示您描述的相同日志行。我怀疑如果您进行上述更改,可能会在不解决核心问题的情况下让这些日志行消失——如果是这样,这表明这些日志行是一个转移注意力的问题。

关于我的案例的一些其他注意事项可能与您的相似或不同:(1) 我正在使用旧的 Xcode 项目和“旧版构建系统”,我最近尝试在其中添加 Siri支持。 (2) 我正在使用 Xcode 13.1 进行构建,但在较旧的 iOS 12.4.6 设备上进行测试,因为这是我们用户的最低 OS 版本。我还没有尝试更新的 iOS 版本。

编辑:请参阅我的其他答案以解决我的问题。我在旧的 iOS 12.4.6 设备上使用 Xcode 13.1 上的遗留构建系统进行了此操作。

检查所有目标(Siri Intent 目标、IntentUI 目标等)的 XCode 构建设置是否都在 Deployment Info 下指定相同的最低 iOS 版本,如下所示:

如果 Intent 目标指定的最低 iOS 版本高于您用于测试的设备,则永远不会调用 IntentHandler,而是调用主目标中的 AppDelegate 方法。在我的例子中,一旦我将部署信息更改为较低的 iOS 版本,IntentHandler 立即开始被调用并且 AppDelegate 方法不再被调用。

此想法归功于 Ondřej Korol:

在为旧 iOS 版本修改旧项目时特别容易发生此问题,因为当您在 XCode 中创建新目标时,它会将 DeploymentInfo 默认为 XCode 的默认值] 即使它与您的主要目标的 DeploymentInfo 不匹配。不幸的是,这是使 SiriKit 开发超级脆弱的众多问题之一。一件小事错了,整件事就悄无声息地失败了!

经过几个月的挫折,我终于开始工作了。我必须创建一个实现了自定义意图的新应用程序。我剪切并粘贴了上面列出的所有相关代码,低看,它起作用了。然后我就煞费苦心的把每一个设置都调试一遍,直到找到问题所在。

Build Phases/Embed App Extensions 下有一个复选框(在项目导航器中选择我的项目,然后选择我的应用程序目标)。此复选框“仅在安装时复制”已选中。取消选中后,一切正常。因为我不知道这是什么,所以我一定是在看到这是解决某人 post 意图的解决方案后绝望地检查了它。

此外,事实证明我在上面的问题陈述中提到的错误并不是任何后果的错误。它们无论如何都会发生。