fix(watch-connectivity): improve WCSession lifecycle and message reliability

This commit is contained in:
2026-08-07 15:32:40 +02:00
parent e64e2353d9
commit 00a8220096
8 changed files with 266 additions and 71 deletions
+52 -18
View File
@@ -3,21 +3,20 @@ import WatchConnectivity
class PhoneConnector: NSObject, ObservableObject, WCSessionDelegate {
static let shared = PhoneConnector()
private var workoutManager: WorkoutManager?
var workoutManager: WorkoutManager? {
didSet {
if let pending = pendingWorkouts {
pending.forEach { workoutManager?.addWorkoutFromPhone($0) }
pendingWorkouts = nil
}
}
}
private var pendingWorkouts: [StoredWorkout]?
private override init() {
super.init()
}
func setup(workoutManager: WorkoutManager) {
self.workoutManager = workoutManager
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
// Send completed workout data to phone
static func sendCompletedWorkout(_ laps: [[String: Any]], duration: TimeInterval, workoutId: String) {
let message: [String: Any] = [
@@ -29,7 +28,8 @@ class PhoneConnector: NSObject, ObservableObject, WCSessionDelegate {
if WCSession.default.isReachable {
WCSession.default.sendMessage(message, replyHandler: nil) { error in
print("Failed to send: \(error)")
print("[PhoneConnector] sendMessage failed, falling back to transferUserInfo: \(error)")
WCSession.default.transferUserInfo(message)
}
} else {
WCSession.default.transferUserInfo(message)
@@ -39,32 +39,66 @@ class PhoneConnector: NSObject, ObservableObject, WCSessionDelegate {
// MARK: - WCSessionDelegate
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("WatchConnectivity activated: \(activationState.rawValue)")
print("[PhoneConnector] Activated: state=\(activationState.rawValue), isReachable=\(session.isReachable)")
}
func sessionReachabilityDidChange(_ session: WCSession) {
print("[PhoneConnector] Reachability changed: isReachable=\(session.isReachable)")
}
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
print("[PhoneConnector] didReceiveMessage (no reply)")
DispatchQueue.main.async { [weak self] in
self?.handleMessage(message)
}
}
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
print("[PhoneConnector] didReceiveMessage (with reply)")
replyHandler(["status": "received"])
DispatchQueue.main.async { [weak self] in
self?.handleMessage(message)
}
}
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any]) {
print("[PhoneConnector] didReceiveUserInfo")
DispatchQueue.main.async { [weak self] in
self?.handleMessage(userInfo)
}
}
private func handleMessage(_ message: [String: Any]) {
guard let type = message["type"] as? String else { return }
guard let type = message["type"] as? String else {
print("[PhoneConnector] No type in message: \(message.keys)")
return
}
print("[PhoneConnector] Received message type: \(type)")
switch type {
case "workoutPlan":
if let data = try? JSONSerialization.data(withJSONObject: message),
let workout = try? JSONDecoder().decode(StoredWorkout.self, from: data) {
workoutManager?.addWorkoutFromPhone(workout)
guard let data = try? JSONSerialization.data(withJSONObject: message) else {
print("[PhoneConnector] Failed to serialize message")
return
}
print("[PhoneConnector] JSON data: \(String(data: data, encoding: .utf8) ?? "nil")")
do {
let workout = try JSONDecoder().decode(StoredWorkout.self, from: data)
print("[PhoneConnector] Decoded workout: \(workout.name ?? "nil"), sets: \(workout.sets.count)")
if let manager = workoutManager {
manager.addWorkoutFromPhone(workout)
} else {
print("[PhoneConnector] workoutManager not set, queuing workout")
if pendingWorkouts == nil { pendingWorkouts = [] }
pendingWorkouts?.append(workout)
}
} catch {
print("[PhoneConnector] Decode error: \(error)")
}
default:
break
print("[PhoneConnector] Unknown type: \(type)")
}
}
}