Search code examples
iosobjective-cxcodeplistwatchkit

Watchkit -application: handleWatchKitExtensionRequest error


I'm getting the following error when requesting a dictionary of XML items via the above method:

> NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]

I have no problem passing an NSDictionary consisting of an NSMutableArray of NSStrings.

From the interface controller:

- (void) requestFeedsFromPhone
{
    [WKInterfaceController openParentApplication:@{@"request":@"feeds"}
                                       reply:^(NSDictionary *replyInfo, NSError *error) {

                                           // the request was successful
                                           if(error == nil) {

                                               // get the array of items
                                               NSMutableDictionary *tempDictionary = replyInfo[@"feeds"];


                                               NSLog(@"tempDictionary: %@", tempDictionary[@"feedsArray"]);
                                               self.feeds = tempDictionary[@"feedsArray"];
                                               [self setupTable];
                                           }
                                           else{
                                               NSLog(@"ERROR: %@", error);
                                           }
                                       }];
}

In the app delegate:

- (void) application:(UIApplication *)application
handleWatchKitExtensionRequest:(NSDictionary *)userInfo
           reply:(void (^)(NSDictionary *))reply
{

    MasterViewController *mainController = (MasterViewController*)  self.window.rootViewController;

    //this is the troublesome line - calling this method results in the error
    NSDictionary *feedsDictionary = [mainController returnFeedsDictionary];

    reply(@{@"feeds": feedsDictionary});
}

In the MasterViewController:

-(NSDictionary *) returnFeedsDictionary
{

    NSURL *url = [NSURL URLWithString:@"http://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss"];
    SeparateParser *separateParser = [[SeparateParser alloc] initWithURL:url];
    [separateParser parse];
    NSArray *tempArray = [separateParser returnFeeds];
    return @{@"feedsArray": tempArray};

}

The returnFeeds method returns an NSMutableArray of NSMutableDictionarys filled with NSMutableStrings (title, link, imageURL, etc).

I'm assuming my problem is that some of my data isn't property list compliant but I thought arrays, strings, and dictionaries were acceptable.


Solution

  • You are hitting the Law of Leaky Abstractions.

    Your iPhone-App and WatchKit-Extension run as two separate processes and two separate sandboxes. The OpenParentApplication/handleWatchKitExtensionRequest/reply mechanism is but a convenience mechanism that Apple provides to wrap intra-process-communication.

    The Abstraction Leak:

    reply(feedsDictionary)

    The returned dictionary can only contain

    • NSData
    • NSString
    • NSDate
    • NSArray
    • NSDictionary

      If your return dictionary contains anything beyond that, like a custom class instance, then you get that crappy error saying reply() was never called, even though you did!



    Solution 1: Serialize your object and send it from app-to-Watch, and then de-serialize on watch (aka Marshalling/Unmarshalling).

    • This requires using NSCoding, as mentioned in an above post: Serialize to NSData* stream, send reply(dataStream) and then de-serialize in the WatchKit Extension.
    • NSCoding is a pain. You need to use NSKeyedArchiver explicitly to serialize before sending, and NSKeyedUnarchiver to de-serialize tutorial here.
    • Requires a lot of cruft code to support encode/Decode within the class Object itself (to satisfy NSCoder)

    Solution 2: Simply pass a string to act as Key, and instantiate your object inside of the Watch Extension (instead of passing the fat object instance itself)

    • Allow WatchKit Extension to compile against your class files, via clicking on the {class}.m, pressing command+option+1, and clicking the "...WatchKit Extension" checkbox under Target Membership (you need to do this anyway, even with solution 1)

    • e.g. Your WatchKit Extension would alloc/init a new instance of the model, rather than trying to pass that across

    • e.g. You would Pass the URL, as a string, and the WatchKit extension would make NSURLConnection call.