WatchConnectivity:WCSession.transferUserInfo 在手表上成功,但数据从未达到 iPhone

WatchConnectivity: WCSession.transferUserInfo succeeds on a watch but the data never reaches an iPhone

上下文

我正在使用 Xcode 12.3.

为 iOS 应用程序(即它不是独立的 watchOS 应用程序)构建一个 watchOS 应用程序

我的 watchOS 应用程序需要根据手表上的用户操作向 iOS 发送少量数据(包含字符串和日期的结构),然后 iOS 应用程序将将接收到的数据保存在自己的存储中。来自手表的数据不需要立即传输,但也不能丢失,最终必须到达 iOS 应用程序。我认为 WCSessiontransferUserInfo(_:) 方法最适合我的需要。

问题

当我在 watchOS 模拟器上的默认 WCSession 上调用 transferUserInfo_: 时,一切都会成功,但数据永远不会到达对应的 iOS 应用程序。

我使用 Xcode 12.3、iOS 14.3 模拟器和配对的 watchOS 7.2 模拟器。

我确保在应用程序启动时激活 WCSession 并检查它是否已成功激活,并在尝试传输用户信息之前检查会话是否处于活动状态。

奇怪的是,我可以使用 WCSession 的 updateApplicationContext(_:) 方法将数据从 iOS 传输到 watchOS,但无法将数据从手表传输回 iOS应用

代码

我已经建立了一个最小的例子来说明这个问题。 watchOS 应用程序显示一个按钮,当用户点击它时,它应该发送一个随机数到 iOS。 iOS 应用程序应该在屏幕上的文本标签中显示收到的号码。

这是示例 iOS 应用程序的完整代码,它只是一个文件:

import os
import SwiftUI
import WatchConnectivity

class WatchConnectivityService: NSObject, ObservableObject {
    @Published private(set) var receivedNumber: Int
    private let logger = Logger(subsystem: "WCExperimentsiOSApp", category: "WatchConnectivityService")
    private let wcSession: WCSession
    
    override init() {
        self.receivedNumber = -9999
        self.wcSession = WCSession.default
        super.init()
        if WCSession.isSupported() {
            wcSession.delegate = self
            wcSession.activate()
        }
    }
}

extension WatchConnectivityService: WCSessionDelegate {
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        logger.notice("WCSession activationDidCompleteWith state: \(activationState), error: \(error?.localizedDescription ?? "nil")")
    }
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        logger.notice("WCSession sessionDidBecomeInactive")
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        logger.notice("WCSession sessionDidDeactivate")
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        logger.notice("Received userInfo: \(userInfo)")
        if let number = userInfo["number"] as? Int {
            DispatchQueue.main.async {
                self.receivedNumber = number
            }
        }
    }
}

@main
struct WatchConnectivityExperimentsApp: App {
    @StateObject var service = WatchConnectivityService()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(service)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var service: WatchConnectivityService
    var body: some View {
        Text("Received number: \(service.receivedNumber)")
    }
}

iOS 应用程序启动后,我可以看到有关成功激活 WCSession 的日志消息,如下所示:“WCSession activationDidCompleteWith state: activated, error: nil”。

以下是 watchOS 扩展的完整代码:

import os
import SwiftUI
import WatchConnectivity

class WatchConnectivityService: NSObject, ObservableObject {
    private let wcSession: WCSession
    private let logger = Logger(subsystem: "WCExperimentsWatchApp", category: "WatchConnectivityService")
    
    override init() {
        self.wcSession = WCSession.default
        super.init()
        if WCSession.isSupported() {
            wcSession.delegate = self
            wcSession.activate()
        }
    }
    
    func sendNumberToiOS() {
        guard wcSession.activationState == .activated else {
            logger.error("Error attempting to transfer user info: WCSession is not activated")
            return
        }
        let randomNumber = Int.random(in: 1...1000)
        wcSession.transferUserInfo(["number": randomNumber])
    }
}

extension WatchConnectivityService: WCSessionDelegate {
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        logger.notice("WCSession activationDidCompleteWith state: \(activationState), error: \(error?.localizedDescription ?? "nil")")
    }
    
    func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
        logger.notice("WCSession didFinish userInfoTransfer: \(userInfoTransfer.userInfo), error: \(error?.localizedDescription ?? "nil")")
    }
}

@main
struct WatchConnectivityExperimentsApp: App {
    @StateObject var service = WatchConnectivityService()
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
                    .environmentObject(service)
            }
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var service: WatchConnectivityService
    var body: some View {
        VStack {
            Button(action: { service.sendNumberToiOS() }, label: {
                Text("Send random number to iOS app")
            })
        }
    }
}

在 watchOS 应用程序中,我还看到一条日志消息,表明 WCSession 在启动时已成功激活。 当我点击手表模拟器上的按钮时,我看到 WCSessionDelegate 方法 func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) 被调用并且它没有报告任何错误。例如:"WCSession didFinish userInfoTransfer: ["number": 683], error: nil".

之后,我在连接手表模拟器的Console.app中看到如下日志:

Process: wcd (Foundation) Subsystem: com.apple.foundation.filecoordination Category: claims Message: Write options: 8 -- URL: Library/Application<decode: mismatch for [%20S] got [SCALAR sz:8]>upport/com.apple.watchconnectivity/E88632F5-57F5-49F9-9CE8-98C1AA6C31CB/UserInfoTransfers/contents.index -- file:///Users/myusername/Library/Developer/CoreSimulator/Devices/753CCF4F-FFAD-4416-BBC6-E23234D0A500/data/Containers/Data/Application/4802344B-0A89-493C-82B3-60D41C76B8B2/ -- purposeID: 2F068AB7-4DC3-4A75-9745-A74AD9179CA4 -- claimID: FF99C583-0CED-44D0-8403-BD342FCE6CAC

Process: WCExperimentsWatchApp Extension Subsystem: com.apple.foundation.filecoordination Category: claims Message: Read options: 1 -- URL: Library/Application<decode: mismatch for [%20S] got [SCALAR sz:8]>upport/com.apple.watchconnectivity/E88632F5-57F5-49F9-9CE8-98C1AA6C31CB/UserInfoTransfers/contents.index -- file:///Users/myusername/Library/Developer/CoreSimulator/Devices/753CCF4F-FFAD-4416-BBC6-E23234D0A500/data/Containers/Data/Application/4802344B-0A89-493C-82B3-60D41C76B8B2/ -- purposeID: D53FBF3C-8B2C-48FB-9390-9058CAD4C757 -- claimID: 62292E97-3529-415A-B6B3-1768387D67B4

在 iOS 方面,WCSessionDelegate 方法 session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) 永远不会被调用,并且 iOS 应用程序永远不会从手表接收任何数据。

我尝试了不同的模拟器,尝试重置模拟器的所有内容和设置,但没有效果。不幸的是,我无法使用真正的 Apple Watch 对其进行测试,因为我没有。

有人可以建议我是否做错了什么吗?如何正确地将 userInfo 从手表发送到 iPhone?

提前致谢。

我重现了您的示例项目,发现它产生了与您遇到的相同的故障。我还发现将通信方法更改为 sending/receiving 一条消息而不是 userInfo(两者都是字典)解决了模拟器中的问题。

我对您的示例所做的更改..

在 phone 应用中:

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        logger.notice("Received userInfo: \(message)")
        if let number = message["number"] as? Int {
            DispatchQueue.main.async {
                self.receivedNumber = number
            }
        }
    }

在手表应用中:

    func sendNumberToiOS() {
        logger.notice("WCSession isReachable: \(self.wcSession.isReachable)")
        guard wcSession.activationState == .activated else {
            logger.error("Error attempting to transfer user info: WCSession is not activated")
            return
        }
        let randomNumber = Int.random(in: 1...1000)
        wcSession.sendMessage(["number": randomNumber], replyHandler: nil) { (error) in
            self.logger.error("Error sending message: \(error.localizedDescription)")
        }
    }

Apple's docs 最后包括以下警告:

Warning

Always test Watch Connectivity data transfers on paired devices. The Simulator app doesn’t support the transferUserInfo(_:) method.

transferFile(_:metadata:)也是如此。