Apple Watch 数据传输:来与回
There and back again: Data transfer on Apple Watch
2021年6月7日
一句话判断
如果你还在用 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。
调试时注意观察 WCSession 的 activationState 和 isReachable 状态变化。很多数据传输的 bug 其实是 session 未正确激活导致的,建议在 sessionDidBecomeInactive 和 sessionDidDeactivate 中加入状态日志。
还有什么值得关注
- watchOS 8 中
WCSession的后台传输队列有了更高的优先级,传输速度比 watchOS 7 有提升。 - 新增的
HKLiveWorkoutBuilder可以配合WCSession实现更好的实时运动数据同步体验。 - 如果只需要在 Watch 端读数据而不需要写回,考虑直接在 Watch 上用 CloudKit 查询,绕过
WCSession的限制。