Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WatchOS2 WCSession sendMessage doesn't wake iPhone on background

Tags:

ios

watchos-2

This is being tested on both Simulator and real physical device iphone5s. I tried to use WCSession sendMessage to communicate from WatchOS2 extension to iPhone iOS9 code. It works well when iphone app is running either in the foreground and background mode.

But If I kill the iPhone app (not running app at all), then I always got errorHandler timeout. So Watch cannot communicate with iPhone anymore.

"Error Domain=WCErrorDomain Code=7012 "Message reply took too long." UserInfo={NSLocalizedDescription=Message reply took too long., NSLocalizedFailureReason=Reply timeout occured.}".

I think it supposed to wake iPhone app in the background.

Any idea what to work around this problem or fix it? Thank you!

like image 665
Frank Wang Avatar asked Oct 14 '15 22:10

Frank Wang


Video Answer


2 Answers

It is important that you activate the WCSession in your AppDelegate didFinishLaunchingWithOptions method. Also you have to set the WCSessionDelegate there. If you do it somewhere else, the code might not be executed when the system starts the killed app in the background.

Also, you are supposed to send the reply via the replyHandler. If you try to send someway else, the system waits for a reply that never comes. Hence the timeout error.

Here is an example that wakes up the app if it is killed:

In the WatchExtension:

Setup the session. Typically in your ExtensionDelegate:

func applicationDidFinishLaunching() {
    if WCSession.isSupported() {
        let session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()
    }
}

And then send the message when you need something from the app:

if WCSession.defaultSession().reachable {
    let messageDict = ["message": "hello iPhone!"]
    WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
        print(replyDict)
        }, errorHandler: { (error) -> Void in
        print(error)
    }
}

In the iPhone App:

Same session setup, but this time also set the delegate:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    ...
    if WCSession.isSupported() {
        let session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()
    }
}

And then implement the delegate method to send the reply to the watch:

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
    replyHandler(["message": "Hello Watch!"])
}

This works whenever there is a connection between the Watch and the iPhone. If the app is not running, the system starts it in the background.

I don't know if the system waits long enough until you received your data from iCloud, but this example definitely wakes up the app.

like image 53
joern Avatar answered Oct 19 '22 06:10

joern


After hours of trying and hint from @jeron. I finally figured out the problem myself.

In my session:didReceiveMessage delegate method, I have two calls. 1.replyHandler call. 2. I have an async process running (RXPromise) in my case, It nested quite a few RXPromise callbacks to fetch various data from cloud service. I didn't pay attention to it, because it is supposed to call and return right away. But now that I commented out RXPromise block all together, it can wake up iOS app in the background every time.

Finally I figure out the trouble make is because after RXPromise call, it is not guaranty to be landed back to main thread anymore. And I believe session:didReceiveMessage has to be return on the main thread. I didn't see this mentioned anywhere on the Apple documentation.

Final solution:

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

    replyHandler(@{ @"schedule" : @"OK" });

    dispatch_async(dispatch_get_main_queue(), ^{
      Nested RXPromise calls.....
    });

}
like image 10
Frank Wang Avatar answered Oct 19 '22 07:10

Frank Wang