You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

跨自然日时HealthKit WorkoutSession锻炼结束失败问题

Apple Watch WorkoutSession跨天结束失败:“锻炼并非当前状态”错误排查与解决

我之前也遇到过类似的跨天WorkoutSession问题,结合HealthKit的文档和实际调试经验,来帮你分析下这个问题:

问题还原

你遇到的场景是:用户在第1天23:30启动锻炼,次日00:30调用end(_:)方法时失败,触发workoutSession(_:didFailWithError:)代理回调,错误提示“锻炼并非当前状态”。

你提供的核心代码片段:

func start(_ workoutSession: HKWorkoutSession) { 
    healthStore.start(workoutSession) 
}

func end(_ workoutSession: HKWorkoutSession) { 
    healthStore.end(workoutSession) 
}

func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) { 
    showAlertWith(title: "WorkoutSession Failure", message: "tell Gary this: \(error) he'll fix it") 
}

问题分析

首先可以明确:你的代码实现本身没有错误,这更可能是HealthKit处理跨日期WorkoutSession的边缘场景Bug(你提交雷达报告的做法非常正确)。

导致这个问题的核心原因大概有两个:

  • HealthKit的WorkoutSession依赖系统日期上下文,午夜日期切换时,系统后台可能会隐性重置或标记旧会话为“非活跃”状态,此时调用end(_:)时,HealthStore会认为该会话已不属于当前活跃状态。
  • Apple Watch的后台执行限制会在跨天时触发资源回收,可能导致WorkoutSession被系统挂起甚至终止,后续的end(_:)调用自然会触发状态错误。

可行的临时解决办法

针对这个跨天场景,你可以尝试以下几种方案来规避问题:

1. 提前检测跨天并拆分会话

在锻炼过程中定时检查当前时间与午夜的差距,当距离午夜还有1-2分钟时,主动结束当前会话并立即启动新会话,让两个会话分别归属不同日期:

func checkForMidnightTransition() {
    let calendar = Calendar.current
    let now = Date()
    let nextMidnight = calendar.startOfDay(for: now.addingTimeInterval(86400))
    let timeUntilMidnight = nextMidnight.timeIntervalSince(now)
    
    // 距离午夜还有2分钟时拆分会话
    if timeUntilMidnight <= 120, let currentSession = currentWorkoutSession {
        end(currentSession)
        // 创建并启动新会话,复用原有的活动类型、位置类型等配置
        let newSession = HKWorkoutSession(activityType: currentSession.activityType, locationType: currentSession.locationType)
        newSession.delegate = self
        start(newSession)
        currentWorkoutSession = newSession
    }
}

可以把这个检查方法放在锻炼中的定时回调(比如每分钟执行一次)里。

2. 调用end前检查并修复会话状态

在调用end(_:)前先校验会话状态,若状态异常则尝试恢复后再结束:

func end(_ workoutSession: HKWorkoutSession) {
    switch workoutSession.state {
    case .running:
        healthStore.end(workoutSession)
    case .paused:
        // 先恢复会话,延迟片刻再结束确保状态更新
        healthStore.resume(workoutSession)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.healthStore.end(workoutSession)
        }
    case .ended, .failed:
        // 会话已处于结束/失败状态,无需重复调用
        break
    default:
        // 其他异常状态,尝试强制结束
        healthStore.end(workoutSession)
    }
}

3. 错误回调中添加重试逻辑

在错误回调中捕获“会话非活跃”的错误,添加重试机制:

func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
    guard let hkError = error as? HKError else {
        showAlertWith(title: "WorkoutSession Failure", message: "tell Gary this: \(error) he'll fix it")
        return
    }
    
    // 匹配“锻炼并非当前状态”对应的错误码
    if hkError.code == .workoutSessionNotActive {
        // 最多重试2次,间隔1秒
        var retryCount = 0
        let retryEndSession = {
            retryCount += 1
            self.healthStore.end(workoutSession)
            if retryCount < 2 {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: retryEndSession)
            }
        }
        retryEndSession()
    } else {
        showAlertWith(title: "WorkoutSession Failure", message: "tell Gary this: \(error) he'll fix it")
    }
}

总结

你的代码逻辑完全符合HealthKit的调用规范,这个问题属于Apple HealthKit的边缘场景Bug。上述临时方案可以帮助你规避跨天结束失败的问题,同时你提交的雷达报告也会帮助Apple更快定位并修复这个问题。

内容的提问来源于stack exchange,提问作者GarySabo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:36:49