There and back again: Data transfer on Apple Watch
watchOS 进阶 28m

Apple Watch 数据传输:来与回

There and back again: Data transfer on Apple Watch

2021年6月7日

在 Apple 官方观看视频

一句话判断

如果你还在用 WCSession.sendMessage 做 Watch-iPhone 数据同步,这场 Session 就是来劝你换方案的——不同场景有不同的最优解,没有银弹。

这场 Session 讲了什么

Apple Watch 和 iPhone 之间的数据传输,一直是 watchOS 开发中的核心痛点。这场 Session 系统梳理了所有可用的传输机制,并给出了明确的场景划分:什么时候该用 WCSession,什么时候该用 CloudKit,什么时候该走 Transferable 协议。

Session 用一个健身 app 作为贯穿全场的示例。运动开始时,Watch 需要快速同步配置到 iPhone(低延迟);运动过程中,心率等传感器数据需要持续推送(高频小包);运动结束后,完整的训练记录需要可靠地同步到 iCloud(大块数据)。

每种场景对应不同的 API 选择。sendMessage 适合实时双向通信但要求两端 app 都在前台;transferUserInfo 可以后台传输但不保证实时性;updateApplicationContext 只保留最新状态,适合配置同步;CloudKit 则适合跨设备的持久化数据。

值得深挖的点

为什么 sendMessage 不再是默认选择

很多开发者习惯用 sendMessage 做所有事情,但它在 watchOS 上的可靠性取决于太多因素:蓝牙连接状态、两端 app 的生命周期、系统资源压力。Session 明确建议只在「需要即时响应」的场景下使用它,比如遥控器、实时对战。对于数据同步类需求,transferUserInfo 配合后台队列是更稳的方案,虽然延迟更高但保证了可达性。

CloudKit 作为 Watch 同步中间层的利弊

把 CloudKit 当作 Watch 和 iPhone 的数据管道听起来很美好——无需配对、自动跨设备。但 CloudKit 的网络延迟在弱网环境下可能达到数秒级别,而且 CKRecord 的序列化开销对小数据包来说过于沉重。Session 建议只在数据量大、需要持久化的场景(如训练历史、健康报告)才走 CloudKit,实时性要求高的数据仍然走 WCSession 本地通道。

代码片段

// 使用 transferUserInfo 后台传输数据
// 适合:运动结束后同步完整训练记录
import WatchConnectivity

class DataSyncManager: NSObject, WCSessionDelegate {
    let session = WCSession.default
    
    override init() {
        super.init()
        session.activate()
    }
    
    // 发送训练数据到 iPhone(后台可靠传输)
    func sendWorkoutData(_ workout: WorkoutData) {
        let userInfo: [String: Any] = [
            "type": "workout_complete",
            "duration": workout.duration,
            "heartRates": workout.heartRateSamples,
            "calories": workout.calories
        ]
        
        // transferUserInfo 会排队等待,即使对方 app 未运行
        session.transferUserInfo(userInfo)
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
        // iPhone 端收到数据后的处理
        guard let type = userInfo["type"] as? String,
              type == "workout_complete" else { return }
        // 处理训练记录...
    }
}
// 使用 updateApplicationContext 同步最新状态
// 适合:只关心最新配置,旧值可以丢弃
func syncLatestConfig() {
    let context: [String: Any] = [
        "workoutType": "running",
        "goalType": "distance",
        "goalValue": 5.0
    ]
    
    do {
        try session.updateApplicationContext(context)
    } catch {
        print("同步配置失败: \(error)")
    }
}

// 对方收到后会覆盖之前的 context
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
    DispatchQueue.main.async {
        // 用最新配置更新 UI
    }
}
// sendMessage 用于实时通信
// 适合:遥控器、实时数据流
func sendRealtimeHeartRate(_ bpm: Double) {
    guard session.isReachable else {
        // 对方不可达,降级为 transferUserInfo
        return
    }
    
    session.sendMessage([
        "heartRate": bpm,
        "timestamp": Date().timeIntervalSince1970
    ], replyHandler: { response in
        // 收到 iPhone 确认
    })
}

最佳实践

根据数据特征选择传输方式,不要一个 API 打天下。可以建一个 DataTransferStrategy 的抽象层,根据数据大小、实时性要求、可靠性要求自动选择底层传输机制。在弱网或蓝牙断开的场景下,要有降级策略:sendMessage 失败时自动切换到 transferUserInfo

调试时注意观察 WCSessionactivationStateisReachable 状态变化。很多数据传输的 bug 其实是 session 未正确激活导致的,建议在 sessionDidBecomeInactivesessionDidDeactivate 中加入状态日志。

还有什么值得关注

  • watchOS 8 中 WCSession 的后台传输队列有了更高的优先级,传输速度比 watchOS 7 有提升。
  • 新增的 HKLiveWorkoutBuilder 可以配合 WCSession 实现更好的实时运动数据同步体验。
  • 如果只需要在 Watch 端读数据而不需要写回,考虑直接在 Watch 上用 CloudKit 查询,绕过 WCSession 的限制。
WWDC 2021