-->

Watchkit , openParentApplication with WatchKit Ext

2019-01-07 00:48发布

问题:

First times doesn't work "Null"( before open App in iPhone )

and some times doesn't work but i want one loop or timer for repeat this request for get result :

here is my code

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
    // Temporary fix, I hope.
    // --------------------
    __block UIBackgroundTaskIdentifier bogusWorkaroundTask;
    bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
    }];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
    });
    // --------------------

    __block UIBackgroundTaskIdentifier realBackgroundTask;
    realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        reply(nil);
        [[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
    }];

    // Kick off a network request, heavy processing work, etc.

    // Return any data you need to, obviously.
    // reply(nil);
    reply(@{@"Confirmation" : @"Text was received."});

    [[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];

    //  NSLog(@"User Info: %@", userInfo);




}

Watch App Code

- (void)willActivate {
    // This method is called when watch view controller is about to be visible to user
    [super willActivate];




    NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:@"MyCamande", @"OK", nil];

    [InterfaceController openParentApplication:dictionary reply:^(NSDictionary *replyInfo, NSError *error) {
        NSLog(@"Reply received by Watch app: %@", replyInfo);
    }];

}

how can recall for get finally result

回答1:

Well, I would not recommend you using anything, related to network operations on watch itself. First of all because Apple does not recommend to do it for obvious reasons. The only network thing that is performed on the watch directly is loading images.

I have been struggling with network operations and watch for like a week and came to a conclusion, that the most stable way to do it right now is not obvious.

The main issue is that WKInterfaceController.openParentApplication(...) does not work as expected. One can not just request to open iPhone app and give back the response as is. There are tons of solutions stating that creating backgound thread in - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply would work just fine, but it actually does not. The problem is that this method has to send reply(...); right away. Even creating synchronious requests won't help, you will keep receiving "error -2 iPhone application did not reply.." like 5 times our of 10.

So, my solution is following:

You implement:

func requestUserToken() {
        WKInterfaceController.openParentApplication(["request" : "token"], reply: responseParser)
    }

and parse response for error that might occur if there's no response from iPhone.

On iOS side

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


    __block UIBackgroundTaskIdentifier watchKitHandler;
    watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
                                                                   expirationHandler:^{
                                                                       watchKitHandler = UIBackgroundTaskInvalid;
                                                                   }];

    NSString *request = userInfo[@"request"];

    if ([request isEqualToString:@"token"])
    {
        reply(@{@"token" : @"OK"});

        [PSWatchNetworkOperations.shared loginUser];
    } 

    dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1 ), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
        [[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
    } );
}

This code just creates a background thread that forces iPhone to send a network request. Let's imagine you would have a special class in your iPhone app that would send these requests and send the answer to watch. For now, this is only accomplishable using App Groups. So you have to create an app group for your application and watchkit extension. Afterwards, I would recommend using MMWormhole in order to establish communication between your app and extension. The manual is pretty self-explaining.

Now what's the point of all this. You have to implement sending request to server and send response through wormhole. I use ReactiveCocoa, so example from my code is like this:

- (void)fetchShoppingLists
{
    RACSignal *signal = [PSHTTPClient.sharedAPIClient rac_GET:@"list/my" parameters:@{@"limit":@20, @"offset":@0} resultClass:PSShoppingListsModel.class];
    [signal subscribeNext:^(PSShoppingListsModel* shoppingLists) {
        [self.wormHole passMessageObject:shoppingLists identifier:@"shoppingLists"];
    }];

    [signal subscribeError:^(NSError *error) {
        [self.wormHole passMessageObject:error identifier:@"error"];
    }];
}

As you see here I send back either response object, or error. Note, that all that you send through wormhole should be NSCoding-compatible.

Now on the watch you'll probably parse response like this:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)

    PSWatchOperations.sharedInstance.requestUserToken()

    PSWatchOperations.sharedInstance.wormhole.listenForMessageWithIdentifier("token", listener: { (messageObject) -> Void in
        // parse message object here
        }
    })

}

So, to make a conclusion. You send request to parent application to wake up from background and start async operation. Send reply() back immediately. When you receive answer from operation send notification that you've got response. Meanwhile listen to response in your watchExtension.

Sorry, that was a lot of text, but I just hope it helps keep one's ass cool, because I've spent a lot of nerves on that.



回答2:

May be you can try to explain the exact problem a little more clearly. But one thing you may want to do regardless is to make the openParentApp call in awakeWithContext: instead of willActivate.