【问题标题】:Notify WatchKit app of an update without the watch app requesting it通知 WatchKit 应用程序更新而无需手表应用程序请求
【发布时间】:2015-05-02 18:31:00
【问题描述】:

我知道 WKInterfaceController openParentApplicationhandleWatchKitExtensionRequest 方法可以让手表应用打开父应用并发送/接收数据。

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

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

【问题讨论】:

    标签: ios watchkit apple-watch


    【解决方案1】:

    背景

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

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

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

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

    达尔文来救援!

    正如你想象的那样,这类问题对开发人员来说并不新鲜,恰当的说法是进程间通信。所以在 OS X 和 iOS 中有...达尔文通知机制。最简单的使用方法是从CFNotificationCenter 类中实现一些方法。

    示例

    当使用 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(),然后我们指定通知名称、发布通知的对象、字典对象(使用 DarwinNotifyCenter 时忽略,最后一个参数是问题的答案:立即交付?

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

    总结一下

    MMWormhole 是一个非常优秀且编写良好的类,即使测试涵盖了大部分(如果不是全部)代码。它很容易使用,只需要记得在之前设置你的 AppGroups。 但是,如果您不想将第三方代码导入您的项目,或者出于其他原因不想使用它,您可以使用此答案中提供的实现。 特别是如果您不想/不需要在 iPhone 和 Watch 之间交换数据。

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

    希望对您有所帮助。

    【讨论】:

    • 这可能是我曾经对 SO 提出的问题最有帮助的答案。谢谢!
    • 讲得真好!也许我从这个概念衍生的设计模式对其他人有帮助:我用它在 Obj-C 中创建了一个 NSObject 类别“DarwinNotification”,我可以在其中将代码嵌入到 C 中。一个 Bridging-Header.h 文件提供了我的 Swift 类从这个类别中访问方便的方法:registerToDarwinNotification、unregisterFromDarwinNotification、postDarwinNotification 和 didReceiveNSNotification。我的概念证明在第一次尝试时就成功了,没有任何麻烦。谢谢!
    • 在 Swift 中也能得到这个答案吗?和OP有同样的问题。谢谢!
    • 我们应该从哪个方法调用 registerToNotification?
    【解决方案2】:

    我相信您现在可能已经解决了您的问题。但是使用“watchOS 2”有更好的方法,无需使用第三方类。 您可以使用 Watch Connectivity Class 的WCSessionsendMessage:replyHandler:errorHandler: 方法。即使您的 iOS 应用未运行,它也能正常工作。

    更多信息你可以参考this blog.

    【讨论】:

      【解决方案3】:

      上面 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) 来保存信息。当 Watch 上的控制器激活时,它会检查 NSUserDefaults 并在必要时更新视图。

      【讨论】:

        【解决方案4】:

        使用 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 应用需要在前台运行。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-09
          • 2014-11-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多