web-dev-qa-db-fra.com

WCSession sendMessage: code d'erreur replyHandler 7014 (WCErrorCodeDeliveryFailed)

J'ai une application Watch OS 2 qui communique avec l'application iOS via la méthode WCSession, sendMessage:replyHandler:errorHandler:

L’application iOS répond correctement mais j’obtiens parfois l’erreur avec le code 7014 du domaine WCErrorDomain: "La charge utile n’a pas pu être livrée"

Cela se produit plus souvent lorsque l'application iOS n'est pas au premier plan.

Je ne trouve pas de solution à ce problème, j'espère que l'un de vous connaît une solution à ce problème

18
gsempe

Pour ceux qui ont des problèmes sur iOS10 beta 6 et GM et que vous utilisez Swift3, la solution consiste à modifier l'en-tête de la fonction de délégué dans l'application iOS comme suit:

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

Notez le @escaping et le Any au lieu du type AnyObject.

10
Peter Robert

Dans mon cas, j'ai dû implémenter les deux délégués:

  1. Celui sans aucun replyHandler

    func session(_ session: WCSession,
                     didReceiveMessage message: [String : Any])
    
  2. Celui avec replyHandler

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

Si vous envoyez un message sans replyHandler, le premier délégué s'exécutera.
Si vous envoyez un message avec une replyHandler, le deuxième délégué s'exécutera.


Dans certains cas, je n'envoyais qu'un message et dans d'autres, j'envoyais un message et j'attendais une réponse de la contrepartie.
MAIS ... je n'avais mis en place que le deuxième délégué -_- 

Quoi qu'il en soit, finalement pour réduire le code en double, j'ai implémenté une méthode commune et je me suis retrouvé avec:

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
}

Regardez l'OS 4

5
staticVoidMan

Essayez celui-ci, cela a résolu mon problème. Dans InterfaceController, ajoutez les méthodes suivantes pour transmettre les données au téléphone.

-(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];

    }
}

Du côté du téléphone, ajoutez les codes suivants pour recevoir les données de la montre.

Ajoutez les éléments suivants dans didFinishLaunchingWithOptions. 

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

Ajoutez maintenant le 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){

                }
            }
        }
    }
}

J'espère que cela vous aide :)

3
Vishnu Kumar. S

Désolé, je n'ai pas assez de réputation pour commenter les réponses ... Mon problème a été résolu avec la réponse de Peter Robert: Avec Swift 3, WCErrorCodeDeliveryFailed est apparu et la solution changeait simplement AnyObject en Any sur le replyHandlers. 

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

J'éprouvais la même chose et le déplacement de l'initialisation de WCSession (définition du délégué et activation) plus tard au cours du cycle de vie de l'application a résolu le problème.

J'ai eu l'activation de WCSession dans les délégués de l'application didFinishLaunching et l'avoir là a brisé la communication. Le déplacement de l'initialisation de WCSession plus tard dans l'application a à nouveau permis aux communications de fonctionner.

1
mraty

Dans mon cas, j'ai mis WCSessionDelegate (côté iOS) dans une classe séparée et l'initialise en tant que variable locale. Le changer eninstance globalevariable a résolu le problème.

Donc, mon code iOS était:

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

Remplacé par ci-dessous pour le faire fonctionner:

var handler: SessionHandler!

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

Vérifier le délégué connecté correct?

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

Remarque: Vérifiez que session.delegate = self; est défini sur self.

0
Vineesh TP

Travailler sur une application et avoir exactement le même comportement. Je suis assez sûr d'avoir regardé partout dans mon code et de n'avoir rien trouvé de mal. Ma meilleure hypothèse est que cela doit être un bogue avec WatchConnectivity.

La solution de contournement de mon gestionnaire d’erreurs actuel tente simplement de recharger des données sur cette erreur particulière. Pas très beau, mais ça marche bien. 

Vous voudrez peut-être essayer quelque chose de similaire?

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

METTRE À JOUR:

Pour toute personne travaillant avec WatchConnectivity; J'ai besoin d'un "hack" similaire pour tester la variable session.reachable

J'ai remarqué que mon application parvient à envoyer un message avant que la session ne soit accessible. J'essaie donc simplement de recharger les données (renvoyer le message) plusieurs fois avant de dire à l'utilisateur que son téléphone est hors de portée.

UPDATE 2: L'exemple ci-dessus utilise .sessionWatchStateDidChange(); le problème n'est donc pas que .sendMessage() est déclenché trop tôt car il n'attend pas l'ack de connexion. Cela doit être un bug, car il ne se produit pas à chaque fois, il pète comme 1 message sur 100.

0
Mikael Hellman

J'ai constaté que le fait de placer le code de réponse en tant que première chose à exécuter résout ce problème (possible en raison du délai d'attente?).

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])

}
0
Tom Coomer

Assurez-vous que votre session est toujours active. Par exemple, j'ai eu une autre vue qui faisait partie des tests, puis je suis revenue sur la vue initiale et je me demandais pourquoi la session n'était plus active.

- (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];
}}

Ci-dessus l'a fait pour moi. Si elle avait été placée dans le contexte awakeWithContext, stupide moi ....

0
user6555550

Ce scénario couvrira plusieurs cas d'utilisation. S'il vous plaît regardez ces étapes, cela m'aide beaucoup. 

1 - Comprenez que chaque périphérique doit avoir sa propre instance WCSession configurée et les délégués appropriés configurés. 

2 - implémentez WCSessionDelegate uniquement à un seul endroit sur chaque périphérique , ej. sur l'application iOS sur AppDelegate, sur watchOS sur ExtensionDelegate. Ceci est très important car avec la WCSession appropriée configurée sur watchOS mais sur un iPhone implémenté à deux endroits différents, ej. sur le délégué de l'application, puis sur le premier viewcontorllweer de l'application, (dans mon cas) conduit à un comportement instable et c'est la raison principale pour laquelle parfois l'application iOS cesse de répondre lorsque le message est reçu. 

3 - il est conseillé de réactiver la session uniquement sur l'application hôte. Voici un exemple d'application iOS avec un seul 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");
}

dernière chose, écrivez la signature de méthode appropriée, si vous avez besoin d'une réponse pour envoyer des données de watch, prenez la signature de méthode qui a répondu: ... Selon Apple, les méthodes suivantes 


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

a une priorité plus élevée et est transmise immédiatement. Tous les messages reçus par votre application sont remis au délégué de session en série sur un fil d'arrière-plan.

Ne perdez donc pas de temps à envoyer l'objet de réponse sur mainQueue sur l'appDelegate iOS, attendez que la réponse sur votre watchOS soit de retour et changez-la en thread principal pour mettre à jour votre interface utilisateur en conséquence. 

0
Boris Ch.F

Dans Swift 3, j'ai résolu l'implémentation de didReceiveMessage avec cette signature: 

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

Vous devrez peut-être (vérifier et) implémenter que votre délégué WCSession a implémenté la méthode suivante. J'ai eu cette erreur en raison de manquer l'implémentation. 

- (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];
}
0
karim