通知 WatchKit 应用更新,而无需手表应用请求

Notify WatchKit app of an update without the watch app requesting it

我知道 WKInterfaceController openParentApplicationhandleWatchKitExtensionRequest 手表应用程序打开父应用程序和 send/receive 数据的方法的功能。

但是这个怎么样...在用户正在使用父应用程序并在父应用程序中执行操作(即更改背景颜色)的情况下,我将如何立即通知手表应用程序并也在手表上执行相关操作?

我相信 MMWormhole 在这个例子中就足够了,这是我应该采用的最佳方法还是有替代方法?

背景

首先让我们总结一下我们所知道的。 我们有

  • 运行 在 iPhone 上的应用(我将其称为 iPhone 应用)
  • Watch 上 运行 的应用...特别是
    • UI 运行 在手表上
    • 运行 在 iPhone 上作为扩展的代码。

第一行和最后一行对我们来说最重要。是的,扩展程序随您的 iPhone 应用程序一起发送到 AppStore,但是这两件事可以 运行 在 iOS 操作系统中分开。因此,Extension 和 iPhone app 是两个不同的进程 - 运行 在 OS.

中的两个不同程序

因此,我们不能使用 [NSNotificationCenter defaultCenter],因为当您尝试 NSLog() defaultCenter on iPhone 和 defaultCenter in Extension 时,它们将具有不同的内存地址。

达尔文来拯救!

如您所想,这种问题对开发人员来说并不陌生,它的正确术语是进程间通信。所以在 OS X 和 iOS 中有...达尔文通知机制。最简单的使用方法是实现 CFNotificationCenter class.

中的几个方法

例子

使用 CFNotificationCenter 时,您会发现它与 NSNotificationCenter 非常相似。我的猜测是 NSNotif.. 是围绕 CFNotif.. 构建的,但我没有证实这个假设。现在,进入正题。

因此,假设您要从 iPhone 向 Watch 来回发送通知。我们应该做的第一件事是注册通知。

- (void)registerToNotification
{    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceivedNSNotification) name:@"com.example.MyAwesomeApp" object:nil];

    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), didReceivedDarwinNotification, CFSTR("NOTIFICATION_TO_WATCH"), NULL, CFNotificationSuspensionBehaviorDrop);
}

你可能想知道我为什么要为 NSNotificationCenter 添加观察者?为了完成我们的任务,我们需要创建一些循环,您稍后会看到它。

至于第二种方法。

CFNotificationCenterGetDarwinNotifyCenter() - 获取达尔文通知中心

(__bridge const void *)(self) - 通知观察者

didReceivedDarwinNotification - callBack 方法,当对象收到通知时触发。 基本上和 NSNotification

中的 @selector 是一样的

CFSTR("NOTIFICATION_TO_WATCH") - 通知的名称,与 NSNotification 中的相同,但这里我们需要 CFSTR 方法将字符串转换为 CFStringRef

最后两个参数 objectsuspensionBehaviour - 在我们使用 DarwinNotifyCenter 时都被忽略了。

太棒了,所以我们注册成为了观察员。因此,让我们实现我们的回调方法(其中有两种,一种用于 CFNotificationCenter,一种用于 NSNotificationCenter)。

void didReceivedDarwinNotification()
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"com.example.MyAwesomeApp" object:nil];
}

现在,如您所见,此方法不以 - (void)Name... 开头。为什么?因为它是 C 方法。你明白为什么我们在这里需要 NSNotificationCenter 了吗?从 C 方法我们无法访问 self。一种选择是声明指向自己的静态指针,如下所示:static id staticSelf 分配它 staticSelf = self 然后从 didReceivedDarwinNotification 使用它:((YourClass*)staticSelf)->_yourProperty 但我认为 NSNotificationCenter 是更好的方法。

那么在响应您的 NSNotification 的选择器中:

- (void)didReceivedNSNotification
{
    // you can do what you want, Obj-C method
}

当我们最终注册为观察者时,我们可以从 iPhone 应用发送一些东西。

为此我们只需要一行代码。

CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("NOTIFICATION_TO_WATCH"), (__bridge const void *)(self), nil, TRUE);

可以在您的 ViewController 或模型中。

同样,我们要获取CFNotificationCenterGetDarwinNotifyCenter(),然后我们指定通知的名称,posting通知的对象,字典对象(使用DarwinNotifyCenter时被忽略,最后一个参数是问题的答案:立即发货?

以类似的方式,您可以将通知从 Watch 发送到 iPhone。出于显而易见的原因,我建议使用不同的通知名称,例如 CFSTR("NOTIFICATION_TO_IPHONE") 以避免 iPhone 向 Watch 和自身发送通知的情况。

总结

MMWormhole 非常好,写得很好 class,即使测试涵盖了大部分(如果不是全部)代码。它易于使用,只需记住之前设置您的 AppGroups 即可。 但是,如果您不想将第三方代码导入到您的项目中,或者出于其他原因不想使用它,则可以使用此答案中提供的实现。 特别是如果您不希望 to/need 在 iPhone 和 Watch.

之间交换数据

还有第二个好项目LLBSDMessaging。它基于伯克利套接字。更复杂,基于更底层的代码。这里是 link 到冗长但写得很好的博客 post,你会在那里找到 link 到 Github。 http://ddeville.me/2015/02/interprocess-communication-on-ios-with-berkeley-sockets/.

希望对您有所帮助。

上面Ivp的回答很好。但是,我想补充一点,使用通知可能很棘手,我想分享我的经验。

首先,我在方法"awakeWithContext"中添加了观察者。问题:多次发出通知。所以,我在添加观察者之前添加了 "removeObserver:self"。问题:当 "self" 不同时,观察者不会被移除。 (另见 here。)

我最终将以下代码放入 "willActivate":

// make sure the the observer is not added several times if this function gets called more than one time
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.toWatch.todo.updated" object:nil];
CFNotificationCenterRemoveObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), CFSTR( "NOTIFICATION_TO_WATCH_TODO_UPDATED" ), NULL );

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( didReceivedNSNotificationTodo ) name:@"com.toWatch.todo.updated" object:nil];
CFNotificationCenterAddObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), didReceivedDarwinNotificationTodo, CFSTR( "NOTIFICATION_TO_WATCH_TODO_UPDATED" ), NULL, CFNotificationSuspensionBehaviorDrop );

我还在 "didDeactivate" 中添加了以下内容:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.toWatch.todo.updated" object:nil];
CFNotificationCenterRemoveObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), CFSTR( "NOTIFICATION_TO_WATCH_TODO_UPDATED" ), NULL );

如果在 Watch 应用处于非活动状态时向其发送通知,则不会发送该通知。

所以,除了上面的通知机制,它可以通知活动的 Watch 应用程序在 iPhone 上完成的更改,我使用 NSUserDefaults 和一个通用应用程序组 (more info) 来保存信息。当手表上的控制器激活时,它会检查 NSUserDefaults 并在必要时更新视图。

我相信你现在可能已经解决了你的问题。但是 "watchOS 2" 有更好的方法而不使用第三方 类。 您可以使用手表连接 Class 的 sendMessage:replyHandler:errorHandler: method of WCSession。即使您的 iOS 应用不是 运行.

,它也能正常工作

更多信息可以参考this blog.

使用 WatchOS 2,您可以sendMessage这样的方法;

父应用程序

然后导入WatchConnectivity

将此添加到 AppDelegate 中的 didFinishLaunchingWithOptions 方法;

if #available(iOS 9.0, *) {
    if WCSession.isSupported() {
        let session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()

        if !session.paired {
            print("Apple Watch is not paired")
        }
        if !session.watchAppInstalled {
            print("WatchKit app is not installed")
        }
    } else {
        print("WatchConnectivity is not supported on this device")
    }
} else {
     // Fallback on earlier versions
}

然后在你的通知功能中;

func colorChange(notification: NSNotification) {
     if #available(iOS 9.0, *) {
        if WCSession.defaultSession().reachable {
           let requestValues = ["color" : UIColor.redColor()]
           let session = WCSession.defaultSession()

           session.sendMessage(requestValues, replyHandler: { _ in
                    }, errorHandler: { error in
                        print("Error with sending message: \(error)")
                })
            } else {
                print("WCSession is not reachable to send data Watch App from iOS")
            }
     } else {
         print("Not available for iOS 9.0")
     }
 }

观看应用程序

不要忘记导入 WatchConnectivity 并将 WCSessionDelegate 添加到您的 InterfaceController

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

    // Create a session, set delegate and activate it
    if (WCSession.isSupported()) {
        let session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()
    } else {
        print("Watch is not supported!")
    }
}

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) { 
    if let deviceColor = message["color"] as? UIColor {
        // do whatever you want with color
    }
}

为此,您的 Watch App 需要在前台运行。