sendMessageData does not call didReceiveMessageData on the counterpart

I would need to return an array of dictionaries when polling the main app from the WatchKit extension. Yet it seems the only plain version of it just uses Dictionaries. So I tried packing the array in an Archive and send it instead through:

public func sendMessageData(data: NSData, replyHandler: ((NSData) -> Void)?, errorHandler: ((NSError) -> Void)?)

and receiving it by:

func session(session: WCSession,     didReceiveMessageData messageData: NSData,     replyHandler: (NSData) -> Void)

Yet, differently from the dictionary version, the latter function gets not called. What might be wrong? The version at: https://developer.apple.com/library/watchos/documentation/WatchConnectivity/Reference/WCSessionDelegate_protocol/index.html#//apple_ref/occ/intfm/WCSessionDelegate/session:didReceiveMessageData:replyHandler: reports a different format that does not compile, though. Those are the two functions:


   // watckit extension
     func poll() {
        if WCSession.defaultSession().reachable{
            let requestValue = ["command": "buspoll"]
            let data = NSKeyedArchiver.archivedDataWithRootObject(requestValue)
            if let safeSession = session{
                if safeSession.reachable {
                    safeSession.sendMessageData(data, replyHandler: {[unowned self] (resultData) -> Void in
                        let reply = NSKeyedUnarchiver.unarchiveObjectWithData(resultData) as! [Dictionary<String, String>]
                        self.myTable.setNumberOfRows(reply.count, withRowType: "WKBusRow")
                        for index in 0..<reply.count {
                            let row=self.myTable.rowControllerAtIndex(index) as! WKBusArrivalRow
                            let busDestination = reply[index]["destination"]
                            let timeLocation = reply[index]["time"]
                            row.bus.setText(busDestination)
                            print("destination=\(busDestination)")
                            row.location.setText(timeLocation)
                            print("time=\(timeLocation)")
                        }
                        }, errorHandler: { (error) -> Void in
                            print("error: \(error)")
                    })
                }
            }
        }
    }
//MainApp
    public func session(session: WCSession,
        didReceiveMessageData messageData: NSData,
        replyHandler: (NSData) -> Void) {
        var data=NSData()
        let message = NSKeyedUnarchiver.unarchiveObjectWithData(messageData) as! Dictionary<String, String>
        var replyValues = [Dictionary<String, String>]()
        print("ricevuto " + message["command"]!)
        switch message["command"]!{
            case "buspoll":
            for index in 0..<busData.count {
                replyValues[index]["destination"] = String(format: "%@: %@", busData[index], destionationsData[index])
                replyValues[index]["time"] = String(format: "%@ min. at %@", minutesData[index], locationData[index])
            }
            data = NSKeyedArchiver.archivedDataWithRootObject(replyValues)
        default:
            break
        }
        replyHandler(data)
    }
Answered by fbartolom in 79371022

The issue was due to the fact I opened the WCSession on the helper class instead of the AppDelegate, and that prevented the didReceiveMessageData from being triggered.

You can simply add your array of dictionaries to another dictionary and send it via

func sendMessage(_ message: [String : AnyObject],
    replyHandler replyHandler: (([String : AnyObject]) -> Void)?,
    errorHandler errorHandler: ((NSError) -> Void)?)

That is a nice idea I shall of course test, yet I am still in the black why the NSData version does not work.

Unfortunately not even this works, I suspect there might be some problems in the objective_c app delegate method:

#ifdef __IPHONE_9_0
-(void) session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message replyHandler:(void (^)(NSDictionary<NSString *,id> * _Nonnull))replyHandler {
    [[WatchkitCommunicator sharedInstance] session:session didReceiveMessage:message replyHandler:replyHandler];
}
#endif

Yet I do not see anything funny, of course the class adopts the WCSessionDelegate protocol.

That is how the two functions came out following your advice:

    func poll() {
        if WCSession.defaultSession().reachable{
            let requestValue = ["command": "buspoll"]
            let session=WCSession.defaultSession()
            session.sendMessage(requestValue, replyHandler: {[unowned self] (mainDictionary) -> Void in
                let reply=mainDictionary["response"] as! Dictionary<String, AnyObject>
                self.myTable.setNumberOfRows(reply.count, withRowType: "WKBusRow")
                for index in 0..<reply.count {
                    print("count=\(reply.count)")
                    let row=self.myTable.rowControllerAtIndex(index) as! WKBusArrivalRow
                    let busDestination = requestValue["destination"]
                    let timeLocation = requestValue["time"]
                    row.bus.setText(busDestination)
                    print("destination=\(busDestination)")
                    row.location.setText(timeLocation)
                    print("time=\(timeLocation)")
                }
                }, errorHandler: { (error) -> Void in
                    print("error: \(error)")
            })
        }
    }
and
    public func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void){
        var replyValues = [Dictionary<String, AnyObject>()]
        switch message["command"] as! String{
        case "buspoll":
            for index in 0..<busData.count {
                replyValues[index]["destination"] = String(format: "%@: %@", busData[index], destionationsData[index])
                replyValues[index]["time"] = String(format: "%@ min. at %@", minutesData[index], locationData[index])
            }
        default:
            break
        }
        let containingDictionary=Dictionary(dictionaryLiteral: ("response", replyValues))
        replyHandler(containingDictionary)
    }

As a matter of fact I even put a local notification in the appDelegate to see if the control passed through there, but it was not delivered. SO I suspect the problem is in the objective-c code, but I ignore what it might be. At present I would not be so willing to translate the full apps delegate to Swift, if not absolutely necessary.

Why are you using "the objective_c app delegate method"?

Here is an example in Swift:

import UIKit
import WatchConnectivity

class ConnectivityHelper: NSObject, WCSessionDelegate {
    var session: WCSession!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        if (WCSession.isSupported()) {
            session = WCSession.defaultSession()
            session.delegate = self;
            session.activateSession()
        }
    }

    func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
        let value = message["Message"] as? String
        dispatch_async(dispatch_get_main_queue()) {
            // do something
        }

        replyHandler(["Message":"Oh yeah!"])
    }
}


And in Objective-C:


ConnectivityHelper.h

#import <Foundation/Foundation.h>
#import <WatchConnectivity/WatchConnectivity.h>

@interface ConnectivityHelper : NSObject <WCSessionDelegate>

@end


ConnectivityHelper.m

#import "ConnectivityHelper.h"

@implementation ConnectivityHelper

- (id) init {

    if (self = [super init]) {

        if ([WCSession isSupported]) {
            WCSession *session = [WCSession defaultSession];
            session.delegate = self;
            [session activateSession];
        }
    }

    return self;
}

- (void)session:(nonnull WCSession *)session didReceiveMessage:(nonnull NSDictionary<NSString *,id> *)message replyHandler:(nonnull void (^)(NSDictionary<NSString *,id> * _Nonnull))replyHandler {
     dispatch_async(dispatch_get_main_queue()) {
          // do something
     }

     NSDictionary *replyDict = [NSDictionary dictionaryWithObjectsAndKeys:@"Oh yeah!", @"Message", nil];
     replyHandler(replyDict);
}

@end


Just load ConnectivityHelper in your AppDelegate.

As a matter of fact I ported the appDelegate to Swift, it took half a day but it was worthwhile. Now the callback is regularly called but I have a new problem in returning a described in the appropriate ticket.

Perhaps the issue was due to the fact I opened the session on the helper class instead of on the delegate ad that prevented the calling of the delegate function.

Accepted Answer

The issue was due to the fact I opened the WCSession on the helper class instead of the AppDelegate, and that prevented the didReceiveMessageData from being triggered.

sendMessageData does not call didReceiveMessageData on the counterpart
 
 
Q