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

问题描述

上下文

我正在使用 Xcode 12.3 为 iOS 应用程序构建 watchOS 应用程序(即它不是独立的 watchOS 应用程序)。

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

问题

当我在 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,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,error: Error?)调用并且它没有报告错误。例如:"WCSession didFinish userInfoTransfer: ["number": 683],error: nil"。

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

进程:wcd(基金会)子系统: com.apple.foundation.filecoordination 类别:索赔 消息:写 options: 8 -- URL: Library/Applicationupport/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-66B8B41C/ -- 目的 ID:2F068AB7-4DC3-4A75-9745-A74AD9179CA4 -- 声明 ID:FF99C583-0CED-44D0-8403-BD342FCE6CAC

流程:WCExperimentsWatchApp 扩展子系统: com.apple.foundation.filecoordination 类别:索赔消息:阅读 选项: 1 -- URL: Library/Applicationupport/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-66B8B41C/ -- 目的 ID:D53FBF3C-8B2C-48FB-9390-9058CAD4C757 -- claimID:62292E97-3529-415A-B6B3-1768387D67B4

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

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

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

提前致谢。

解决方法

我复制了您的示例项目,发现它产生了与您遇到的相同的失败。我还发现将通信方法更改为发送/接收消息而不是 userInfo(两者都是字典)解决了模拟器中的问题。

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

在手机应用中:

    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)")
        }
    }