Harden Apple Watch snapshot synchronization

This commit is contained in:
2026-08-17 18:06:09 +02:00
parent 5c88d3ace0
commit 2a59803b6c
8 changed files with 372 additions and 64 deletions

View File

@@ -85,6 +85,12 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
func sceneBecameActive() {
restoreSnapshot()
guard WCSession.isSupported() else { return }
let session = WCSession.default
session.activate()
if session.activationState == .activated {
receive(session.receivedApplicationContext, session: session, authoritative: true)
}
}
func sceneBecameInactive() {
@@ -108,7 +114,11 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
}
}
private func receive(_ applicationContext: [String: Any], session: WCSession) {
private func receive(
_ applicationContext: [String: Any],
session: WCSession,
authoritative: Bool = false
) {
try? core.syncStarted()
guard var snapshot = applicationContext[Self.snapshotKey] as? Data else {
try? core.syncFinished()
@@ -117,7 +127,11 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
}
defer { snapshot.resetBytes(in: 0..<snapshot.count) }
do {
let update = try core.applySnapshot(snapshot: snapshot)
let update = if authoritative {
try core.applyAuthoritativeSnapshot(snapshot: snapshot)
} else {
try core.applySnapshot(snapshot: snapshot)
}
switch update.persistence {
case .keep:
break
@@ -128,20 +142,25 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
}
refreshPresentation()
guard !update.receipt.isEmpty else { return }
let acknowledgement = [Self.receiptKey: update.receipt]
if session.isReachable {
session.sendMessage(acknowledgement, replyHandler: nil) { _ in
session.transferUserInfo(acknowledgement)
}
} else {
session.transferUserInfo(acknowledgement)
}
acknowledge(update.receipt, through: session)
} catch {
try? core.syncFailed()
refreshPresentation()
}
}
private func acknowledge(_ receipt: Data, through session: WCSession) {
let acknowledgement = [Self.receiptKey: receipt]
for transfer in session.outstandingUserInfoTransfers
where transfer.userInfo[Self.receiptKey] != nil {
transfer.cancel()
}
session.transferUserInfo(acknowledgement)
if session.isReachable {
session.sendMessage(acknowledgement, replyHandler: nil) { _ in }
}
}
func refreshPresentation(at date: Date = .now) {
presentation = try? core.presentationAt(
unixSeconds: UInt64(date.timeIntervalSince1970)
@@ -160,7 +179,11 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
self.refreshPresentation()
return
}
self.receive(session.receivedApplicationContext, session: session)
self.receive(
session.receivedApplicationContext,
session: session,
authoritative: true
)
}
}
@@ -169,7 +192,7 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
didReceiveApplicationContext applicationContext: [String: Any]
) {
Task { @MainActor [weak self] in
self?.receive(applicationContext, session: session)
self?.receive(applicationContext, session: session, authoritative: true)
}
}