Контекст

Я создаю приложение watchOS для приложения iOS (т. е. это не самостоятельное приложение watchOS) с помощью Xcode 12.3.

Мое приложение watchOS должно отправлять небольшие фрагменты данных (структуры, содержащие строку и дату) в iOS в результате действий пользователя на часах, после чего приложение iOS будет сохранять полученные данные в собственном хранилище. Данные с часов не обязательно должны быть переданы немедленно, но их нельзя и потерять, и в конце концов они должны попасть в приложение iOS. Я решил, что метод transferUserInfo(_:) метода WCSession лучше всего подходит для моих нужд.

Проблема

Когда я вызываю transferUserInfo_: на WCSession по умолчанию на симуляторе watchOS, все проходит успешно, но данные никогда не достигают аналогичного приложения iOS.

Я использую Xcode 12.3, симулятор iOS 14.3 и сопряженный симулятор watchOS 7.2.

.

Я обязательно активирую WCSession при запуске приложения и проверяю, что он успешно активирован, а также проверяю, активна ли сессия, прежде чем пытаться передать информацию о пользователе.

Как ни странно, я могу передать данные из iOS в watchOS с помощью метода updateApplicationContext(_:) WCSession, но не могу передать данные обратно с часов в приложение 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()
        }
    }
}

расширение 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("Полученный номер: \(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("Ошибка при попытке передать информацию о пользователе: WCSession не активирована")
            return
        }
        let randomNumber = Int.random(in: 1...1000)
        wcSession.transferUserInfo(["number": randomNumber])
    }
}

расширение 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("Отправить случайное число в приложение iOS")
            })
        }
    }
}

В приложении watchOS я также вижу сообщение журнала, что WCSession был успешно активирован при запуске. Когда я нажимаю кнопку на симуляторе часов, я вижу, что метод WCSessionDelegate func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) вызывается и не сообщает об ошибках. Например: "WCSession didFinish userInfoTransfer: ["number": 683], error: nil".

После этого я вижу следующие журналы в Console.app, подключенном к симулятору часов:

Процесс: wcd (Foundation) Подсистема: com.apple.foundation.filecoordination Категория: claims Сообщение: Записать опции: 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-60D41C76B8B2/ -- purposeID: 2F068AB7-4DC3-4A75-9745-A74AD9179CA4 -- claimID: 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-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, потому что у меня их нет.

Может ли кто-нибудь подсказать, делаю ли я что-то неправильно? Как мне правильно отправить информацию о пользователе с часов на iPhone?

Заранее спасибо.

Ответы (2)

Документы компании Apple наконец-то включают следующее предупреждение:

Предупреждение

Всегда проверяйте передачу данных Watch Connectivity на сопряженных устройствах. Приложение Simulator не поддерживает метод transferUserInfo(_:) метод.

Это также верно для transferFile(_:metadata:).

Я воспроизвел ваш пример проекта и обнаружил, что в нем произошел тот же сбой, что и у вас. Я также обнаружил, что изменение метода связи на отправку/получение сообщения вместо 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
            }
        }
    }

В приложении Watch:

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

2022 WebDevInsider