ios swift watch-os-3 watchconnectivity

ios - "La respuesta del mensaje tomó demasiado tiempo."-Observe los problemas de conectividad con Watch OS 3



swift watch-os-3 (2)

En mi proyecto, utilizo Watch Connectivity para enviar mensajes ay desde Watch y iPhone. Puedo enviar un mensaje al teléfono y recibir una serie de cadenas al iniciar la aplicación; sin embargo, al usar acciones obtengo el siguiente error;

Error Domain = WCErrorDomain Code = 7012 "La respuesta del mensaje tomó demasiado tiempo".

Así es como se configuran las cosas;

Primero, el reloj envía un mensaje al teléfono y luego el teléfono envía una matriz de cadenas para mostrar en una WKInterfaceTable . Esto a veces funciona al cargar la aplicación. (Busco todos los elementos denominados NSManagedObjects y uso sus propiedades de cadena de title para almacenarlos en una array llamada watchItems .

Sin embargo, tengo una acción en el reloj para eliminar todos los elementos en la matriz y actualizar la tabla con los datos nuevos.

La acción en el reloj utiliza una función sendMessage para enviar el item al teléfono para eliminarlo de la matriz, luego el teléfono envía la matriz recién actualizada al reloj y el reloj actualiza la tabla. Sin embargo, devuelvo la misma matriz o un error.

Bastante simple, así que todo funcionó bien antes de Swift 3 y Watch OS3 / iOS 10; toda la aplicación solía funcionar.

Así es como tengo todo configurado;

Delegado de la aplicación telefónica

import WatchConnectivity class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate { var session : WCSession! var items = [Items]() func loadData() { let moc = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext let request = NSFetchRequest<Items>(entityName: "Items") request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)] request.predicate = NSPredicate(format: "remove == 0", "remove") do { try self.items = moc!.fetch(request) // success ... } catch { // failure print("Fetch failed") } } //WATCH EXTENSION FUNCTIONS //IOS 9.3 /** Called when the session has completed activation. If session state is WCSessionActivationStateNotActivated there will be an error with more details. */ //HAVE TO INCLUDE @available(iOS 9.3, *) func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?){ print("iPhone WCSession activation did complete") } @available(iOS 9.3, *) func sessionDidDeactivate(_ session: WCSession) {} func sessionWatchStateDidChange(_ session: WCSession) {} func sessionDidBecomeInactive(_ session: WCSession) { } //APP DELEGATE FUNCTIONS func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { //Check if session is supported and Activate if (WCSession.isSupported()) { session = WCSession.default() session.delegate = self; session.activate() } return true } } //DID RECIEVE MESSAGE func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Swift.Void) { loadData() func loadItems() { watchItems.removeAll() for a in self.items { watchItems.append(a.title) } } var watchItems = ["1","2","3","4","5"] let value = message["Value"] as? String //This is called when user loads app, and takes some time when using refresh action, sometimes times out if value == "HELLOiPhone/+@=" { print("Hello Message Recieved") loadItems() //send a reply replyHandler( [ "Items" : Items ] ) } //Not sure if receiving but does not delete array and send back to watch if value == "removeALL@+=-/" { for index in self.items { index.remove = 1 //Saves MOC } loadData() loadTasksData() //send a reply replyHandler( [ "Items" : Items ] ) } else { for index in self.items { if index.title == value { index.remove = 1 //Saves MOC } } loadData() loadTasksData() //send a reply replyHandler( [ "Items" : Items ] ) } }

RELOJ

import WatchConnectivity class SimplelistInterfaceController: WKInterfaceController, WCSessionDelegate { /** Called when the session has completed activation. If session state is WCSessionActivationStateNotActivated there will be an error with more details. */ @available(watchOS 2.2, *) public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { //Fetch data is a function which sends a "HELLOiPhone/+@=" message to receive the array and displays in the table. This works fetchData() } var session : WCSession! var items = ["Refresh Items"] override func didAppear() { fetchData() } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() //Check if session is supported and Activate if (WCSession.isSupported()) { session = WCSession.default() session.delegate = self session.activate() } fetchData() } override func awake(withContext context: Any?) { super.awake(withContext: context) fetchData() } @IBAction func refresh() { print("Refresh") //Works but sometimes message is delayed fetchData() } @IBAction func removeAll() { print("Remove All Items is called") if WCSession.default().isReachable { let messageToSend = ["Value":"removeALL@+=-/"] print("/(messageToSend)") session.sendMessage(messageToSend, replyHandler: { replyMessage in if let value = replyMessage["Items"] { self.items = value as! [String] Not receiving message print("Did Recieve Message, items = /(self.items)") } }, errorHandler: {error in // catch any errors here print(error) }) } fetchData() } }


  1. No debe enviar ni recibir objetos de clase personalizados desde un destino (iOS) al segundo objetivo (watchOS), sino que debe enviar / recibir datos en formato de diccionario como [String: Any] y este diccionario debe contener una matriz de sus propiedades personalizadas requeridas. en el par de valores clave en el diccionario simple. Esto podría ser fácilmente decodificable en el lado del reloj.

  2. Debería crear una clase desacoplada que extienda WCSessionDelegate tal como se muestra a continuación, para que esta clase se pueda usar no solo en ExtensionDelegate sino también en cualquier WKInterfaceController.

    class WatchSessionManager: NSObject, WCSessionDelegate { static let sharedManager = WatchSessionManager() private override init() { super.init() self.startSession() } private let session: WCSession = WCSession.default func startSession() { session.delegate = self session.activate() } func tryWatchSendMessage(message: [String: Any], completion: (([String: Any]) -> Void)? = nil) { print("tryWatch /(message)") weak var weakSelf = self if #available(iOS 9.3, *) { if weakSelf?.session.activationState == .activated { if weakSelf?.session.isReachable == true { weakSelf?.session.sendMessage(message, replyHandler: { [weak self] ( response ) in guard let slf = self else {return} //Get the objects from response dictionary completion?(response) }, errorHandler: { [weak self] ( error ) in guard let slf = self else {return} print ( "Error sending message: % @ " , error ) // If the message failed to send, queue it up for future transfer slf.session.transferUserInfo(message) }) } else { self.session.transferUserInfo(message) } }else{ self.session.activate() self.session.transferUserInfo(message) } } else { // Fallback on earlier versions if self.session.activationState == .activated { if self.session.isReachable == true { self.session.sendMessage(message, replyHandler: { ( response ) in //Get the objects from response dictionary completion?(response) }, errorHandler: { ( error ) in print ( "Error sending message: % @ " , error ) // If the message failed to send, queue it up for future transfer self.session.transferUserInfo(message) }) } else { self.session.transferUserInfo(message) } }else{ self.session.activate() self.session.transferUserInfo(message) } } } }

Ahora podría enviar fácilmente un mensaje a su aplicación iOS para despertar y obtener datos de allí (por ejemplo, desde CoreData) usando la función anterior en cualquier WKInterfaceController y el bloque de finalización tendrá sus datos requeridos, tales como

let dict: [String: Any] = ["request": "FirstLoad"] WatchSessionManager.sharedManager.tryWatchSendMessage(message: dict,completion:{ (data) in print(data)})

De la misma manera que debería utilizar este WatchSessionManager en iOS y recibir la solicitud y según la clave solicitada, debe tomar datos del núcleo de almacenamiento / base de datos y enviar la lista de objetos personalizados en un patrón simple de valor clave dentro de la función replyHandler of didreceiveMessage como se muestra a continuación .

func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { var dict: [String: Any] = [String: Any]() replyHandler(dict) //This dict will contain your resultant array to be sent to watchApp. }

En algún momento, la aplicación iOS (estado asesinado) no es alcanzable para WatchApp, para resolver ese problema debe llamar a "tryWatchSendMessage" dentro del temporizador de aproximadamente 3 segundos de intervalo. Y cuando obtenga la conexión de watchApp, debe invalidar el temporizador.

La funcionalidad sendMessage de WatchConnectivity es tan poderosa para activar su aplicación. Debe usarlo de manera optimizada.


Acabo de tratar con mi aplicación WatchOS. Y hubo una situación en la que recibí "La respuesta del mensaje tomó demasiado tiempo".

Luego agregué el controlador de tareas en segundo plano a mi aplicación iOS y comencé a enviar mensajes cada segundo a la aplicación WatchOS. El mensaje contenía UIApplication.shared.backgroundTimeRemaining . Entonces obtengo: 45sec, 44sec, ..., 6sec, 5sec, ... Si el temporizador se ejecuta por debajo de 5 segundos, no se enviarán mensajes a / desde la aplicación de iOS y obtendremos "La respuesta del mensaje demoró demasiado". La solución más fácil fue enviar mensajes en blanco de reloj a teléfono cada vez que el temporizador pasa a menos de 15 segundos. El backgroundTimeRemaining se actualizará nuevamente a 45 segundos: 45, 44, 43, ..., 17, 16, 15, (mensaje en blanco), 45, 44, 43, ...

Espero que ayude a alguien