6

I have two view controllers in my watch extension.Whenever I call

[[WCSession defaultSession] sendMessage:applicationData replyHandler:^(NSDictionary *reply)  {}

I get response only for the first view controller, and error in my second viewcontroller

Error Domain=WCErrorDomain Code=7011 "Message reply failed." 
UserInfo={NSUnderlyingError=0x79f1f100 {Error Domain=WCErrorDomain Code=7010 "Payload contains unsupported type."
UserInfo={NSLocalizedRecoverySuggestion=Only pass valid types., NSLocalizedDescription=Payload contains unsupported type.}}, NSLocalizedDescription=Message reply failed.}

WCSession is initiated in both app and watch extension.Any suggestions?

yoshiiiiiiii
  • 953
  • 8
  • 20

6 Answers6

13

In WCSessionDelegate's - session:didReceiveMessage:replyHandler: method, the replyHandler argument is defined as [String : AnyObject]. The AnyObject portion is misleading. It can only contain a property list data type: NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber. (Under these circumstances, it makes sense why AnyObject was chosen, since those 6 data types do not inherit from a common subclass besides NSObject.)

Usually people mention that NSCoding and NSKeyedArchiver can resolve the issue, but I haven't seen more examples/explanations beyond that.

The thing to note is that the replyHandler dictionary doesn't care about serialization. You could use NSKeyedArchiver, JSON, your own custom encoding, etc. So long as the dictionary only contains those 6 data types replyHandler will be happy. Otherwise you'll see the Payload contains unsupported type. error.

For this reason, you can never call the reply handler like so: replyHandler(["response": myCustomObject), even if myCustomObject implements the NSCoding protocol perfectly.

Summary of encoding choices:

  • NSCoding: The main advantage is that when you unarchive, it'll automatically find the correct class and instantiate it for you including any objects in its subgraph.
  • JSON
  • Custom encoding: One advantage is that your object is not forced to inherit from NSObject, which is sometimes helpful in Swift.

If you do use NSCoding, this is what it'll look like:

iPhone App:

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
    let data = NSKeyedArchiver.archivedDataWithRootObject(myCustomObject)
    replyHandler(["response": data])
}

Watch App:

WCSession.defaultSession().sendMessage([],
    replyHandler: {
        response -> Void in
        let myCustomObject = NSKeyedUnarchiver.unarchiveObjectWithData(response["response"])
    }, errorHandler: nil
)

Note that if you want to recover from a crash when unarchiving objects, you'll need to use the new iOS 9 API, unarchiveTopLevelObjectWithData, which throws an error if there was a problem.

Note: Your custom object must inherit from NSObject, otherwise you'll get the following error when archiving:

*** NSForwarding: warning: object ... of class 'Foo' does not implement methodSignatureForSelector: -- trouble ahead Unrecognized selector -[Foo replacementObjectForKeyedArchiver:]

Community
  • 1
  • 1
Senseful
  • 86,719
  • 67
  • 308
  • 465
7

"Payload contains unsupported type" likely means you are sending a custom object in your message dictionary. You will need to serialize this data to contain only the supported types (NSNumber, NSDate, NSString, NSData, NSArray, and NSDictionary).

I have a github project that automatically serializes your custom objects into safe ones for watchkit transfer. You can check it out here.

lehn0058
  • 19,977
  • 15
  • 69
  • 109
  • Yes,that was the issue.By the way it works perfectly if i send string but it throws an error if I do NSString *str1 = [response valueForKey:@"d"]; NSData *data1 = [str1 dataUsingEncoding:NSUTF8StringEncoding]; NSMutableDictionary *mainDict1 = [NSJSONSerialization JSONObjectWithData:data1 options:NSJSONReadingMutableContainers error:nil]; – yoshiiiiiiii Oct 06 '15 at 16:47
  • Do you have your github project in objective c – yoshiiiiiiii Oct 06 '15 at 16:50
  • No,its not working second time,i calling the same method 2 times. – yoshiiiiiiii Oct 06 '15 at 17:05
  • Where I can find the supported types please? It just say it supports NSDictionary, but when I put an image in a dictionary as a keyValue, it unsupported. means that key must be a NSString, but keyValue could be any object. – Andy Darwin Oct 16 '15 at 02:43
  • Convert image to NSData – yoshiiiiiiii Oct 23 '15 at 04:49
2

Using NSKeyedArchiver/NSKeyedUnarchiver you can serialize any object that implements NSCoding.

To archive:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject: entries];

Where entries is an array of objects that implement NSCoding.

To unarchive:

NSArray *entries = [NSKeyedUnarchiver unarchiveObjectWithData:data];

NSCoding is a protocol with two methods you have to implement like this.

-(id) initWithCoder: (NSCoder *)decoder {
  if(self = [super init]){
    self.yourpoperty = [decoder decodeObjectForKey:@"PROPERTY_KEY"];
  }
  return self;
}

- (void) encodeWithCoder: (NSCoder *)encoder {
  [encoder encodeObject:self.yourpoperty forKey:@"PROPERTY_KEY"];
}
1

I solved it by sending JSON string in dictionary format directly to the call back method from the iPhone app Appdelegate

- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler {

instead of converting the JSON dictionary to normal dictionary. And converting this JSON dictionary to normal dictionary in the watch viewcontroller call back methods

[[WCSession defaultSession] sendMessage:applicationData
                           replyHandler:^(NSDictionary *reply) {}

Since I was calling this methods from two different viewcontrollers in watch,sending normal dictionary from the iPhone app to watch works fine the first time but for some reasons I was getting the errors in the question if I send the dictionary from the iPhone app to watch for the second view controller of watch.

yoshiiiiiiii
  • 953
  • 8
  • 20
0

I saw this error when accidentally transporting over 400 data objects to the watch. Limiting to 20 objects fixed the error.

Chris Hobbs
  • 495
  • 6
  • 5
0

In my case what happened before iOS 10 and XCode 8.0, in my watchkit app code i used:

let infoDictionary = ["request" : "word_detail", "word": self.word, "type": self.type]

and it works grest. When I tested the same code in WatchKit app 2.0 simulator by XCode 8.0, I found watchkit connectivity error showing with unsupported format message.

After lots of debugging I found the solution for it:

let infoDictionary:NSDictionary = ["request" : "word_detail", "word": self.word, "type": self.type]

I had to put just NSDictionary as a typecast and the whole thing works without problem.

Mahmud Ahsan
  • 1,755
  • 19
  • 18