ios watch-os-2 wcsession

ios - WCSession sendMessage: replyHandler error code 7014(WCErrorCodeDeliveryFailed)



watch-os-2 (13)

Tengo una aplicación Watch OS 2 que se comunica con la aplicación iOS mediante el método sendMessage:replyHandler:errorHandler:

La aplicación iOS responde correctamente, pero de vez en cuando recibo el error con el código 7014 del dominio WCErrorDomain : "No se pudo entregar la carga útil"

Ocurre más a menudo cuando la aplicación iOS no está en primer plano.

No encuentro ninguna solución a este problema, espero que alguno de ustedes conozca una solución a este problema


Asegúrese de que su sesión esté siempre activa. Por ejemplo, tuve otra vista que formaba parte de las pruebas y luego regresé a la vista inicial y me preguntaba por qué la sesión ya no estaba activa.

- (void)willActivate { // This method is called when watch view controller is about to be visible to user [super willActivate]; //Setup WCSession if ([WCSession isSupported]) { [[WCSession defaultSession] setDelegate:self]; [[WCSession defaultSession] activateSession]; }}

Arriba lo hice por mí. La primera vez que lo coloqué en el awakeWithContext, estúpido yo ...


Compruebe el delegado conectado ¿correcto?

WCSession* session = WCSession.defaultSession; session.delegate = self; [session activateSession];

Nota: Verifique session.delegate = self; establecer a sí mismo.


En Swift 3 resolví la implementación de didReceiveMessage con esta firma:

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void)


En mi caso, coloco WCSessionDelegate (lado de iOS) en una clase separada y lo inicializo como variable local. Cambiarlo a variable de instancia global solucionó el problema.

Así que mi código iOS fue:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { SessionHandler() }

Cambiado a continuación para que funcione:

var handler: SessionHandler! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { handler = SessionHandler() }


En mi caso, tuve que implementar ambos delegados:

  1. El que no tiene replyHandler

    func session(_ session: WCSession, didReceiveMessage message: [String : Any])

  2. El de replyHandler

    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void)

Si envía un mensaje sin replyHandler se replyHandler el primer delegado.
Si envía un mensaje con replyHandler se replyHandler el segundo delegado.

En algunos casos, solo enviaba un mensaje y, en otros, enviaba un mensaje y esperaba una respuesta de la contraparte.
PERO ... había implementado solo el segundo delegado -_-

De todos modos, para reducir el código duplicado, implementé un método común y terminé con:

func session(_ session: WCSession, didReceiveMessage message: [String : Any]) { handleSession(session, didReceiveMessage: message) } func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { handleSession(session, didReceiveMessage: message, replyHandler: replyHandler) } //Helper Method func handleSession(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: (([String : Any]) -> Void)? = nil) { //Common logic }

Ver OS 4


Es posible que deba (verificar e) implementar que su delegado de WCSession implementó el siguiente método. Recibí este error debido a que falta la implementación.

- (void)session:(WCSession * _Nonnull)session didReceiveMessage:(NSDictionary<NSString *, id> * _Nonnull)replyMessage replyHandler:(void (^ _Nonnull)(NSDictionary<NSString *, id> * _Nonnull replyMessage))replyHandler { NSLog(@"Received. %@", replyMessage); [self processResponse:replyMessage]; }


Estaba experimentando lo mismo y mover la inicialización de WCSession (configurar delegado y activarlo) más adelante en el ciclo de vida de la aplicación solucionó el problema.

Tuve la activación de WCSession en la aplicación. Los delegados terminaron la finalización de la ejecución. Mover la inicialización de WCSession más adelante en la aplicación hizo que las comunicaciones funcionaran de nuevo.


Este escenario cubrirá varios casos de uso. Por favor, mira estos pasos, me ayuda mucho.

1 - Comprenda que cada dispositivo debe tener su propia instancia de WCSession configurada y con los delegados adecuados configurados.

2: implementar WCSessionDelegate solo en un solo lugar en cada dispositivo , ej. en la aplicación iOS en AppDelegate, en watchOS en ExtensionDelegate. Esto es muy importante porque con la WCSession adecuada configurada en watchOS pero en iPhone implementando en dos lugares diferentes, ej. en el delegado de la aplicación y luego en el primer viewcontorllweer de la aplicación, (en mi caso) conducen a un comportamiento inestable y esa es la razón principal por la que algunas veces la aplicación iOS deja de responder cuando se recibe un mensaje del reloj.

3 - es recomendable reactivar la sesión solo en la aplicación host. Este es un ejemplo de mi aplicación iOS con un solo WCSessionDelegate. (AppDelegate)

#pragma mark - WCSessionDelegate - (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(NSError *)error{ if( activationState == WCSessionActivationStateActivated) { NSLog(@"iPhone WKit session Activated"); }else if (activationState == WCSessionActivationStateInactive) { NSLog(@"iPhone WKit Inactive"); }else if (activationState == WCSessionActivationStateNotActivated) { NSLog(@"iPhone WKit NotActivated"); } } - (void)sessionDidBecomeInactive:(WCSession *)session{ /* The session calls this method when it detects that the user has switched to a different Apple Watch. While in the inactive state, the session delivers any pending data to your delegate object and prevents you from initiating any new data transfers. After the last transfer finishes, the session moves to the deactivated state */ NSLog(@"sessionDidBecomeInactive"); if (session.hasContentPending) { NSLog(@"inactive w/ pending content"); } } - (void)sessionDidDeactivate:(WCSession *)session{ // Begin the activation process for the new Apple Watch. [[WCSession defaultSession] activateSession]; //perform any final cleanup tasks related to closing out the previous session. } - (void)sessionReachabilityDidChange:(WCSession *)session{ NSLog(@"sessionReachabilityDidChange"); }

Lo último, escriba la firma del método apropiado, si necesita una respuesta enviando datos desde el reloj, tome la firma del método que responda: ... Según Apple, los siguientes métodos

sendMessage:replyHandler:errorHandler:, sendMessageData:replyHandler:errorHandler:, and transferCurrentComplicationUserInfo:

Tiene una prioridad más alta y se transmite de inmediato. Todos los mensajes recibidos por su aplicación se envían al delegado de la sesión en serie en un hilo de fondo.

Por lo tanto, no pierda el tiempo enviando el objeto de respuesta en mainQueue en la aplicación iOS. Seleccione hasta que tenga la respuesta en su watchOS de nuevo y cámbielo al hilo principal para actualizar su IU en consecuencia.


He encontrado que poner el código de respuesta como la primera cosa que se corrige corrige este problema (¿es posible que se produzca un tiempo de espera?)

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: ([String : Any]) -> Void) { print("Message - received") //Send reply let data = ["receivedData" : true] replyHandler(data as [String : AnyObject]) }


Lo siento, no tengo suficiente reputación para comentar las respuestas. Mi problema se resolvió con la respuesta de Peter Robert: con Swift 3, WCErrorCodeDeliveryFailed apareció y la solución simplemente fue cambiar AnyObject a Any en los answerHandlers.

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { //code replyHandler (answer as [String : Any]) }


Para cualquier persona que tenga problemas con iOS10 beta 6 y GM, y está utilizando Swift3, la solución es cambiar el encabezado de la función de delegado en la aplicación de iOS a lo siguiente:

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {

Tenga en cuenta el @escaping y el Any en lugar del tipo AnyObject.


Prueba este, esto solucionó mi problema. Dentro del InterfaceController agregue los siguientes métodos para pasar los datos al teléfono.

-(void)sendDataToPhone:(NSDictionary* _Nonnull)dictData { if(WCSession.isSupported){ WCSession* session = WCSession.defaultSession; session.delegate = self; [session activateSession]; if(session.reachable) { [session sendMessage:dictData replyHandler: ^(NSDictionary<NSString *,id> * __nonnull replyMessage) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@".....replyHandler called --- %@",replyMessage); // Play a sound in watch [[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess]; }); } errorHandler:^(NSError * __nonnull error) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Error = %@",error.localizedDescription); }); } ]; } else NSLog(@"Session Not reachable"); } else NSLog(@"Session Not Supported"); } #pragma mark - Standard WatchKit delegate -(void)sessionWatchStateDidChange:(nonnull WCSession *)session { if(WCSession.isSupported){ WCSession* session = WCSession.defaultSession; session.delegate = self; [session activateSession]; } }

En el lado del teléfono, agregue los siguientes códigos para recibir los datos del reloj.

Agregue lo siguiente en didFinishLaunchingWithOptions.

// Allocating WCSession inorder to communicate back to watch. if(WCSession.isSupported){ WCSession* session = WCSession.defaultSession; session.delegate = self; [session activateSession]; }

Ahora agregue el WCSessionDelegate.

#pragma mark - WCSession Delegate - (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler { if(message){ NSData *receivedData = [message objectForKey:@"AudioData"]; NSDictionary* response = @{@"response" : [NSString stringWithFormat:@"Data length: %lu",(unsigned long)receivedData.length]} ; replyHandler(response); } } #pragma mark - Standard WatchKit Delegate -(void)sessionWatchStateDidChange:(nonnull WCSession *)session { if(WCSession.isSupported){ WCSession* session = WCSession.defaultSession; session.delegate = self; [session activateSession]; if(session.reachable){ NSLog(@"session.reachable"); } if(session.paired){ if(session.isWatchAppInstalled){ if(session.watchDirectoryURL != nil){ } } } } }

Espero que esto te ayude :)


Trabajar en una aplicación y tener exactamente el mismo comportamiento. Estoy bastante seguro de que he buscado en todas partes en mi código y no he encontrado nada malo. Mi mejor conjetura es que esto debe ser un error con WatchConnectivity .

Mi solución actual para el controlador de errores simplemente intenta recargar datos sobre este error en particular. No es muy bonito, pero funciona bien.

¿Quieres probar algo similar?

func messageErrorHandler(error: NSError) { isLoading = false print("Error Code: /(error.code)/n/(error.localizedDescription)") // TODO: WTF?. Check future releases for fix on error 7014, and remove this... if error.code == 7014 { // Retry after 1.5 seconds... retryTimer = NSTimer.scheduledTimerWithTimeInterval( NSTimeInterval(1.5), target: self, selector: "reloadData", userInfo: nil, repeats: false) return } displayError("/(error.localizedDescription) (/(error.code))", message: "/(error.localizedDescription)") }

ACTUALIZAR:

Para cualquiera que trabaje con WatchConnectivity ; Necesito tener un "truco" similar para probar la variable session.reachable .

Me he dado cuenta de que mi aplicación logra enviar un mensaje antes de poder acceder a la sesión. Así que simplemente trato de recargar datos (reenviar el mensaje) un par de veces antes de decirle al usuario que su teléfono está fuera de su alcance.

ACTUALIZACIÓN 2: El ejemplo anterior está usando .sessionWatchStateDidChange() , por lo que el problema no es que .sendMessage() se .sendMessage() demasiado pronto porque no se espera el .sendMessage() . Esto debe ser un error ya que no está sucediendo cada vez, solo se asusta como 1 por 100 mensajes.