不需要手表应用程序请求,通知WatchKit应用程序更新

18

我了解WKInterfaceController openParentApplicationhandleWatchKitExtensionRequest方法的能力,可以使手表应用程序打开父应用程序并发送/接收数据。

但是,如果用户在使用父应用程序并执行操作(例如更改背景颜色),如何立即通知手表应用程序并在手表上执行相关操作?

我认为MMWormhole在这种情况下就足够了,这是我应该采取的最佳方法还是有替代方案?

4个回答

39

背景

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

  • 在 iPhone 上运行的应用程序(我将其称为)
  • 在 Apple Watch 上运行的应用程序...具体来说
    • 在 Apple Watch 上运行的用户界面
    • 在 iPhone 上作为扩展运行的代码。

对于我们来说,第一个和最后一行是最重要的。是的,扩展与 iPhone 应用程序一起发布到 AppStore 中,但是这两个东西可以在 iOS 操作系统中分别运行。因此,扩展和 iPhone 应用程序是两个不同的进程 - 在操作系统中运行的两个不同程序。

由于这个事实,我们不能使用[NSNotificationCenter defaultCenter],因为当您尝试在 iPhone 上使用 NSLog() defaultCenter,并在扩展中使用 defaultCenter 时,它们将具有不同的内存地址。

Darwin 来拯救了!

正如您可能想象的那样,这种问题对于开发人员来说并不新鲜,它的正确术语是进程间通信。因此,在 OS X 和 iOS 中有 Darwin 通知机制。而使用它的最简单方法是实现CFNotificationCenter类中的几个方法。

示例

当使用 CFNotificationCenter 时,您会发现它看起来非常类似于 NSNotificationCenter。我猜 NSNotif.. 是围绕 CFNotif.. 构建的,但我没有证实这个假设。现在,到重点。

所以让我们假设您想要在 iPhone 和 Apple 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() - 获取Darwin Notify中心

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

didReceivedDarwinNotification - 回调方法,在对象接收通知时触发。 基本上与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或Model中。

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

以类似的方式,您可以从手表向iPhone发送通知。出于明显的原因,我建议使用不同的通知名称,例如CFSTR("NOTIFICATION_TO_IPHONE"),以避免出现这样的情况,例如iPhone向手表和自己发送通知。

总结

MMWormhole是完全正常且编写良好的类,甚至包含了大多数(如果不是全部)代码的测试。它很容易使用,只需记住在设置之前设置AppGroups。 但是,如果您不想将第三方代码导入项目或由于其他原因而不想使用它,则可以使用本答案提供的实现。 特别是如果您不想/需要在iPhone和Watch之间交换数据。

还有第二个不错的项目LLBSDMessaging。它基于Berkeley套接字。更复杂,基于更底层的代码。这是链接到冗长但编写良好的博客文章,您将在那里找到Github的链接。 http://ddeville.me/2015/02/interprocess-communication-on-ios-with-berkeley-sockets/

希望这有所帮助。


2
这可能是我在SO上对问题得到的最有帮助的答案了。谢谢! - Andrew Davis
2
这是一堂很棒的讲座!也许我的设计模式可以从这个概念中得到启发,对其他人有所帮助:我用它创建了一个名为“DarwinNotification”的NSObject类别,它在Obj-C中嵌入了C代码。Bridging-Header.h文件使得我的Swift类可以访问此类别中方便的方法:registerToDarwinNotification、unregisterFromDarwinNotification、postDarwinNotification和didReceiveNSNotification。我的概念验证第一次就成功了,没有任何麻烦。谢谢! - Computerspezl
能否也用Swift回答这个问题?我和楼主遇到了同样的问题。谢谢! - GarySabo
我们应该从哪个方法调用registerToNotification? - SAHM

2
我相信你现在可能已经解决了你的问题。但是使用“watchOS 2”,有更好的方法不需要使用第三方类。你可以使用WCSessionsendMessage:replyHandler:errorHandler: 方法,Watch Connectivity Class。即使你的iOS应用程序没有运行,它也能正常工作。
获取更多信息,你可以参考this blog.

1
上面Ivp的回答很好。但是,我想补充一下使用通知可能会有些棘手,我想分享一下我的经验。
首先,我在方法“awakeWithContext”中添加了观察者。问题是:通知被多次发送。所以,在添加观察者之前,我添加了“removeObserver:self”。问题是:当“self”不同时,观察者将不会被移除。(也可以参见这里。)
最终,我把以下代码放在“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 );

如果在手表应用处于非活动状态时发送通知,则该通知不会被传递。
因此,除了上述的通知机制之外,可以通知活动的手表应用程序iPhone上所做的更改,我使用NSUserDefaults和一个公共应用程序组(更多信息)来保存信息。当手表上的控制器变为活动状态时,它会检查NSUserDefaults并在必要时更新视图。

0

在 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
    }
}

为了实现这一点,你的手表应用需要在前台运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接