如何从 watchOS 2 唤醒 iPhone 应用程序?

How to wake up iPhone app from watchOS 2?

我有一个网络层非常丰富的应用程序,我的 apple watch 应用程序依赖于所有型号。不幸的是,该应用程序的模块化程度不足以使该层在手表应用程序中可用。

我通过使用 openParentApplication 解决了这个问题:唤醒 iPhone 应用程序,执行请求并返回结果。

在 watchOS 2 中,此方法已不存在,我应该使用 WatchConnectivity。使用它的最佳方法是发送 userInfo 字典。

但是如何唤醒 iPhone 应用程序来处理我的请求?要获得有关新用户信息的通知,我必须使用 WCSessionDelegate,为此我需要一个 WCSession 对象。但是我什么时候应该创建它?以及如何唤醒应用程序?

我就此问题询问了 Apple 工程师并得到以下提示:iOS-App 应在后台任务中启动。所以以下对我来说非常有用:

UIApplication *application = [UIApplication sharedApplication];

__block UIBackgroundTaskIdentifier identifier = UIBackgroundTaskInvalid;
dispatch_block_t endBlock = ^ {
    if (identifier != UIBackgroundTaskInvalid) {
        [application endBackgroundTask:identifier];
    }
    identifier = UIBackgroundTaskInvalid;
};

identifier = [application beginBackgroundTaskWithExpirationHandler:endBlock];

将此添加到您的 session:didReceiveMessage:session:didReceiveMessageData: 方法以启动后台任务,超时时间为三分钟。

在 WatchKit 扩展中,您需要使用 WatchConnectivity WCSession sendMessage API。在扩展程序中检查 iOS 应用是否可访问,然后发送消息:

let session = WCSession.defaultSession();
if session.reachable {
    let message = ["foo":"bar"]
    session.sendMessage(message, replyHandler: nil, errorHandler: { (error) -> Void in
        print("send failed with error \(error)")
    })
}

此消息将导致系统在后台唤醒 iOS 应用程序,因此请确保在 iOS 应用程序代码中设置 WCSession,当 运行 在后台(例如:你不想把它放在 UIViewController 的子类的 viewDidLoad 中)以便接收消息。由于您将请求一些信息,您可能希望利用回复块。

这就是您在后台启动 iOS 应用程序的方法,尽管 WatchConnectivity WWDC session 建议尽可能使用 "background" 传输方法。如果您的手表应用程序是 read-only 那么您应该能够使用后台传输在 iOS 设备上排队任何更改,然后它们将在下次运行时传送到手表应用程序。

Swift 下面本杰明·赫尔佐格建议的版本。值得注意的是,虽然我确实选择将 Watch 启动的工作推送到后台任务,但只要我的应用程序在后台,系统就可以很好地唤醒它。在后台任务中完成工作似乎不是必需的,但却是最佳实践。

override init() {
    super.init()
    if WCSession.isSupported() {
        session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()
    }
}

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        let taskID = self.beginBackgroundUpdateTask()
        //Do work here...
        self.endBackgroundUpdateTask(taskID)
    })
    var replyValues = Dictionary<String, AnyObject>()
    let status = "\(NSDate()): iPhone message: App received and processed a message: \(message)."
    print(status)
    replyValues["status"] = status
    replyHandler(replyValues)
}

func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
    return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}

func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
    UIApplication.sharedApplication().endBackgroundTask(taskID)
}