WCSession sendMessage: код ошибки replyHandler 7014 (WCErrorCodeDeliveryFailed)

У меня есть приложение Watch OS 2, которое взаимодействует с приложением iOS с помощью WCSession метода sendMessage:replyHandler:errorHandler:

Приложение iOS отвечает правильно, но время от времени я получаю сообщение об ошибке с кодом 7014 домена WCErrorDomain: «Не удалось доставить полезную нагрузку».

Чаще бывает, когда приложение iOS не на переднем плане.

Я не нахожу решения этой проблемы, надеюсь, кто-то из вас знает решение этой проблемы


person gsempe    schedule 18.10.2015    source источник
comment
Когда возвращается ошибка, есть ли в ней какая-либо основная ошибка (error.userInfo[NSUnderlyingErrorKey])? Было бы хорошо увидеть код, который отправляет сообщение, и реализацию метода делегата, который должен его получать!   -  person ccjensen    schedule 19.10.2015
comment
Привет, вы устранили эту проблему?   -  person Vishnu Kumar. S    schedule 21.10.2015
comment
Привет нет я все еще ищу решение   -  person gsempe    schedule 21.10.2015
comment
У меня такая же проблема, здесь тоже нет решения. Должна быть ошибка в WatchConnectivity ...   -  person Mikael Hellman    schedule 12.12.2015
comment
Вы никогда не отвечали на мои вопросы выше. Также было бы хорошо увидеть класс, в котором вы настраиваете WCSession.   -  person ccjensen    schedule 03.06.2016
comment
Удалось ли вам исправить эту проблему? У меня проблема с Watch OS 3 beta 6. Если вы нашли решение, пожалуйста, помогите   -  person Infaz    schedule 19.08.2016
comment
Я использую XCODE 8 GM с SWIFT 3, а также получаю ошибку 7014 с кодом, который работал в предыдущих версиях ... мысли?   -  person Mavro    schedule 15.09.2016


Ответы (13)


Для тех, у кого есть проблемы с iOS10 beta 6 и GM, и вы используете Swift3, решение состоит в том, чтобы изменить заголовок функции делегата в приложении iOS на следующее:

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

Обратите внимание на @escaping и Any вместо типа AnyObject.

person Peter Robert    schedule 08.09.2016
comment
Ты супергерой! - person Ahmadreza; 14.12.2018

В моем случае мне пришлось реализовать обоих делегатов:

  1. Тот, без каких-либо replyHandler

    func session(_ session: WCSession,
                     didReceiveMessage message: [String : Any])
    
  2. Тот, с replyHandler

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

Если вы отправляете сообщение без replyHandler, то запускается первый делегат.
Если вы отправляете сообщение с replyHandler, то запускается второй делегат.


В некоторых случаях я отправлял только сообщение, а в других случаях я отправлял сообщение и ожидал ответа от контрагента.
НО ... Я реализовал только второй делегат -_-

В любом случае, чтобы уменьшить количество повторяющегося кода, я реализовал общий метод и получил:

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
}

Посмотреть OS 4

person staticVoidMan    schedule 14.02.2018

Попробуйте это, это устранило мою проблему. Внутри InterfaceController добавьте следующие методы для передачи данных на телефон.

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

    }
}

На стороне телефона добавьте следующие коды для получения данных с часов.

Добавьте следующее в didFinishLaunchingWithOptions.

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

Теперь добавьте 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){

                }
            }
        }
    }
}

Надеюсь, это поможет вам :)

person Vishnu Kumar. S    schedule 22.10.2015

Извините, у меня недостаточно репутации, чтобы комментировать ответы. Моя проблема была решена с помощью ответа Питера Роберта: с Swift 3 появился WCErrorCodeDeliveryFailed, и решение просто изменило AnyObject на Any в replyHandlers.

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

У меня было то же самое, и перемещение инициализации WCSession (установка делегата и его активация) позже в жизненном цикле приложения устранило проблему.

У меня была активация WCSession в делегатах приложения didFinishLaunching, и это нарушило связь. После переноса инициализации WCSession в приложении связь снова заработала.

person mraty    schedule 13.06.2016

В моем случае я помещаю WCSessionDelegate (сторона iOS) в отдельный класс и инициализирую его как локальную переменную. Изменение ее на переменную глобального экземпляра решило проблему.

Итак, мой код iOS был:

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

Изменено ниже, чтобы заставить его работать:

var handler: SessionHandler!

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

Возможно, вам потребуется (проверить и) реализовать, что ваш WCSession делегат реализовал следующий метод. Я получил эту ошибку из-за отсутствия реализации.

- (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];
}
person karim    schedule 18.01.2016

Проверить правильность подключения делегата?

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

Примечание. Убедитесь, что для session.delegate = self; установлено значение self.

person Vineesh TP    schedule 02.06.2016

Работают над приложением и ведут себя точно так же. Я почти уверен, что просмотрел всюду в своем коде и не нашел ничего неправильного. Я предполагаю, что это ошибка WatchConnectivity.

Мой текущий обходной путь обработчика ошибок просто пытается перезагрузить данные об этой конкретной ошибке. Не очень красиво, но работает нормально.

Возможно, вы захотите попробовать что-то подобное?

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

ОБНОВИТЬ:

Для всех, кто работает с WatchConnectivity; Мне нужен аналогичный «хак» для тестирования переменной session.reachable.

Я заметил, что моему приложению удается отправить сообщение до того, как сеанс станет доступным. Поэтому я просто пытаюсь перезагрузить данные (повторно отправить сообщение) пару раз, прежде чем сообщить пользователю, что его телефон вне досягаемости.

ОБНОВЛЕНИЕ 2: В приведенном выше примере используется .sessionWatchStateDidChange(), поэтому проблема не в том, что .sendMessage() запускается слишком рано из-за того, что не дожидается подтверждения соединения. Это, должно быть, ошибка, так как это происходит не каждый раз, это просто ужасно, как 1 на 100 сообщений.

person Mikael Hellman    schedule 12.12.2015
comment
Почему бы не использовать обратный вызов делегата, информирующий вас об изменении достижимости или KVO в свойстве достижимости, чтобы знать, когда попытаться отправить еще раз? - person ccjensen; 03.06.2016
comment
Я использую его, приведенный выше фрагмент кода - всего лишь мой обработчик ошибок для '.sendMessageData ()' - person Mikael Hellman; 03.06.2016

Я обнаружил, что установка кода ответа в качестве первого запуска устраняет эту проблему (возможно, вызвано тайм-аутом?).

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

}
person Tom Coomer    schedule 08.09.2016

В Swift 3 я решил реализовать didReceiveMessage с этой подписью:

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

Убедитесь, что ваша сессия всегда активна. У меня, например, было другое представление, которое было частью тестирования, а затем вернулось к исходному представлению, и мне было интересно, почему сеанс больше не активен.

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

Выше сделал это за меня. Если бы он сначала был помещен в awakeWithContext, я глупо ...

person Community    schedule 12.02.2017

Этот сценарий охватывает несколько вариантов использования. Пожалуйста, взгляните на эти шаги, они мне очень помогают.

1. Помните, что каждое устройство должно иметь собственный экземпляр WCSession и настроены соответствующие делегаты.

2 - реализовать WCSessionDelegate только в одном месте на каждом устройстве, например. в приложении iOS в AppDelegate, в watchOS на ExtensionDelegate. Это очень важно, потому что соответствующий WCSession настроен на watchOS, но на iPhone реализован в двух разных местах, например, ej. в делегате приложения, а затем в первом viewcontorllweer приложения (в моем случае) приводит к нестабильному поведению, и это основная причина, по которой иногда приложение iOS перестает отвечать при получении сообщения от часов.

3 - повторную активацию сеанса рекомендуется делать только в хост-приложении. Это пример моего приложения для iOS только с одним 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");
}

последнее, напишите соответствующую подпись метода, если вам нужен ответ, отправляющий данные от часов, возьмите подпись метода, у которого есть ответ: ... Согласно Apple следующие методы


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

имеет более высокий приоритет и передается сразу. Все сообщения, полученные вашим приложением, доставляются делегату сеанса последовательно в фоновом потоке.

Так что не тратьте время на отправку объекта ответа в mainQueue в iOS appDelegate, дождитесь, пока ответ на watchOS вернется, и измените его на основной поток, чтобы соответствующим образом обновить пользовательский интерфейс.

person Boris Ch.F    schedule 04.08.2017