Decipher and deal with common Siri errors
System & Services 进阶 20m

解读和处理常见的 Siri 错误

Decipher and deal with common Siri errors

2020年6月26日

在 Apple 官方观看视频

一句话判断

你的 Intent Handler 返回 .failure 就结束了?这场 Session 讲了十几种不同的错误码和它们对应的用户体验——选对错误码比选对成功码更重要。

这场 Session 讲了什么

大多数开发者在 Intent 处理失败时只返回一个通用的 .failure,但 SiriKit 定义了大量细分的错误码,每个对应不同的用户反馈和处理方式。

Session 按错误类型分类讲解:用户输入相关的错误(内容不存在、参数不完整)、系统状态相关的错误(需要登录、需要更新 App)、网络相关的错误(超时、连接失败)、以及权限相关的错误(用户拒绝了权限)。

核心观点是:精确的错误码能让 Siri 给出更准确的反馈。比如 .failureNoContentViewable 会让 Siri 说”找不到这个内容”,而 .failureRequiringAppLaunch 会让 Siri 提示用户”请在 App 中完成操作”。模糊的 .failure 只会让 Siri 说”出了点问题”。

值得深挖的点

错误码与用户信任。 当 Siri 说”出了点问题,稍后再试”时,用户不知道是自己的问题还是 App 的问题,会降低对 App 和 Siri 的信任。精确的错误反馈(“你的会员已过期”、“这个内容需要付费”)帮助用户理解问题并采取行动。

可恢复错误 vs 不可恢复错误。 可恢复错误(如网络超时)可以让 Siri 建议用户重试。不可恢复错误(如内容已下架)应该直接告知用户并建议替代方案。区分这两类错误对用户体验至关重要。

代码片段

import Intents

// 精确的错误处理
class PlayMediaIntentHandler: NSObject, INPlayMediaIntentHandling {
    func handle(intent: INPlayMediaIntent, completion: @escaping (INPlayMediaIntentResponse) -> Void) {
        // 检查用户登录状态
        guard userManager.isLoggedIn else {
            // 用户未登录,提示打开 App 登录
            let response = INPlayMediaIntentResponse(
                code: .failureRequiringAppLaunch,
                userActivity: makeActivity(for: .loginRequired)
            )
            completion(response)
            return
        }
        
        // 检查内容是否存在
        guard let mediaName = intent.mediaSearch?.mediaName else {
            let response = INPlayMediaIntentResponse(
                code: .failureNoContentViewable,
                userActivity: nil
            )
            completion(response)
            return
        }
        
        searchAndPlay(mediaName: mediaName) { result in
            switch result {
            case .success(let item):
                let response = INPlayMediaIntentResponse(code: .success, userActivity: nil)
                response.nowPlayingInfo = self.buildNowPlayingInfo(for: item)
                completion(response)
                
            case .failure(let error):
                let response: INPlayMediaIntentResponse
                switch error {
                case .contentNotFound:
                    // 内容不存在,建议替代
                    response = INPlayMediaIntentResponse(
                        code: .failureNoContentViewable,
                        userActivity: nil
                    )
                case .subscriptionRequired:
                    // 需要订阅
                    response = INPlayMediaIntentResponse(
                        code: .failureRequiringAppLaunch,
                        userActivity: self.makeActivity(for: .subscriptionPage)
                    )
                case .networkError:
                    // 网络问题,可以重试
                    response = INPlayMediaIntentResponse(
                        code: .failure,
                        userActivity: nil
                    )
                }
                completion(response)
            }
        }
    }
}
// 创建带上下文的 NSUserActivity
// 当用户点击 Siri 的"打开 App"按钮时跳转到对应页面
func makeActivity(for destination: AppDestination) -> NSUserActivity {
    let activity = NSUserActivity(activityType: "com.example.app.\(destination.rawValue)")
    activity.userInfo = ["destination": destination.rawValue]
    activity.title = destination.title
    return activity
}

enum AppDestination: String {
    case loginRequired
    case subscriptionPage
    case contentDetail
    
    var title: String {
        switch self {
        case .loginRequired: return "登录"
        case .subscriptionPage: return "订阅"
        case .contentDetail: return "内容详情"
        }
    }
}

最佳实践

  • 永远不要用通用的 .failure 至少区分”找不到内容”和”需要用户操作”两类错误。
  • 为可恢复错误提供重试建议。 网络超时、服务暂时不可用等错误可以建议用户稍后重试。
  • userActivity 指引下一步操作。 当 Siri 提示”请在 App 中操作”时,通过 userActivity 让 App 打开到正确的页面。
  • 记录错误的分布和频率。 通过分析错误数据找出最常见的问题并优先修复。

还有什么值得关注

  • .failureRequiringAppLaunch 会自动在 Siri 界面上添加”打开 App”按钮
  • 新的错误类型在每年的 SDK 中会增加,查阅最新的 IntentResponse 文档
  • Siri 会在连续失败后建议用户尝试其他方式(如手动打开 App)
  • 错误反馈的多语言适配需要考虑语气和措辞
WWDC 2020