【问题标题】:Using WCSession with more than one ViewController将 WCSession 与多个 ViewController 一起使用
【发布时间】:2015-09-14 22:32:34
【问题描述】:

我发现了许多问题和许多答案,但没有最终的请求示例:

谁能给出最后一个示例在Objective C中,将WCSession 与IOS 应用程序和具有多个ViewController 的Watch 应用程序(WatchOS2)一起使用的最佳实践是什么。

到目前为止,我注意到以下事实:

1.) 在 AppDelegate 的父 (IOS) 应用中激活 WCSession:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //Any other code you might have

    if ([WCSession isSupported]) {
        self.session = [WCSession defaultSession];
        self.session.delegate = self;
        [self.session activateSession];
    }
}

2.) 在 WatchOS2 端使用<WCSessionDelegate>。但其余的对我来说完全不清楚!一些答案是通过在传递的字典中指定键来讨论的,例如:

[session updateApplicationContext:@{@"viewController1": @"item1"} error:&error];
[session updateApplicationContext:@{@"viewController2": @"item2"} error:&error];

其他人正在谈论检索默认会话

WCSession* session = [WCSession defaultSession];
[session updateApplicationContext:applicationDict error:nil];

其他人在谈论不同的队列? “如有必要,客户端有责任分派到另一个队列。分派回主队列。”

我完全糊涂了。所以请举例说明如何将 WCSession 与 IOS 应用程序和 WatchOS2 应用程序与多个 ViewController 一起使用。

我需要它用于以下情况(简化): 在我的父应用程序中,我正在测量心率、锻炼时间和卡路里。在 Watch 应用 1. ViewController 我会在 2. ViewController 上显示心率和锻炼时间。我也会显示心率和燃烧的卡路里。

【问题讨论】:

    标签: ios watchkit watchos-2


    【解决方案1】:

    据我了解,您只需要在Phone -> Watch 方向上进行同步,因此简而言之,您需要一个最低配置:

    电话:

    我相信application:didFinishLaunchingWithOptions: 处理程序是WCSession 初始化的最佳位置,因此在此处放置以下代码:

    if ([WCSession isSupported]) {
        // You even don't need to set a delegate because you don't need to receive messages from Watch.
        // Everything that you need is just activate a session.
        [[WCSession defaultSession] activateSession];
    }
    

    然后在您的代码中测量心率的地方,例如:

    NSError *updateContextError;
    BOOL isContextUpdated = [[WCSession defaultSession] updateApplicationContext:@{@"heartRate": @"90"} error:&updateContextError]
    
    if (!isContextUpdated) {
        NSLog(@"Update failed with error: %@", updateContextError);
    }
    

    更新:

    观看:

    ExtensionDelegate.h:

    @import WatchConnectivity;
    #import <WatchKit/WatchKit.h>
    
    @interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate>
    @end
    

    ExtensionDelegate.m:

    #import "ExtensionDelegate.h"
    
    @implementation ExtensionDelegate
    
    - (void)applicationDidFinishLaunching {
        // Session objects are always available on Apple Watch thus there is no use in calling +WCSession.isSupported method.
        [WCSession defaultSession].delegate = self;
        [[WCSession defaultSession] activateSession];
    }
    
    - (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
         NSString *heartRate = [applicationContext objectForKey:@"heartRate"];
    
        // Compose a userInfo to pass it using postNotificationName method.
        NSDictionary *userInfo = [NSDictionary dictionaryWithObject:heartRate forKey:@"heartRate"];
    
        // Broadcast data outside.
        [[NSNotificationCenter defaultCenter] postNotificationName: @"heartRateDidUpdate" object:nil userInfo:userInfo];
    }
    
    @end
    

    在控制器中的某个位置,我们将其命名为 XYZController1。

    XYZController1:

    #import "XYZController1.h"
    
    @implementation XYZController1
    
    - (void)awakeWithContext:(id)context {
        [super awakeWithContext:context];
    
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUpdatedHeartRate:) name:@"heartRateDidUpdate" object:nil];
    }
    
    -(void)handleUpdatedHeartRate:(NSNotification *)notification {
            NSDictionary* userInfo = notification.userInfo;
            NSString* heartRate = userInfo[@"heartRate"];
            NSLog (@"Successfully received heartRate notification!");
    }
    
    @end
    

    代码未经测试,我只是按原样编写,因此可能会有一些拼写错误。

    我认为现在的主要思想很清楚,转移剩余类型的数据并不是那么艰巨的任务。

    我当前的 WatchConnectivity 架构要复杂得多,但它仍然基于此逻辑。

    如果您仍有任何问题,我们可能会将进一步讨论移至聊天室。

    【讨论】:

    【解决方案2】:

    嗯,这是Greg Robertson 要求的我的解决方案的简化版本。抱歉,它不再在 Objective-C 中了;我只是从现有 AppStore 批准的项目中复制粘贴,以确保不会出错。

    本质上,任何 WatchDataProviderDelegate 都可以挂钩到数据提供者类,因为它为委托提供了数组持有者(而不是一个弱 var)。 传入的 WCSessionData 使用 notifyDelegates() 方法转发给所有委托。

    // MARK: - Data Provider Class
    
    class WatchDataProvider: WCSessionDelegate {
    
        // This class is singleton
        static let sharedInstance = WatchDataProvider()
    
        // Sub-Delegates we'll forward to
        var delegates = [AnyObject]()
    
        init() {
            if WCSession.isSupported() {
                WCSession.defaultSession().delegate = self
                WCSession.defaultSession().activateSession()
                WatchDataProvider.activated = true;
            }
        }
    
        // MARK: - WCSessionDelegate                
    
        public func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
            processIncomingMessage(userInfo)
        }
    
        public func session(session: WCSession, didReceiveApplicationContext applicationContext: [String: AnyObject]) {
            processIncomingMessage(applicationContext)
        }
    
        func processIncomingMessage(dictionary: [String:AnyObject] ) {
            // do something with incoming data<
            notifyDelegates()
        }
    
        // MARK: - QLWatchDataProviderDelegate     
    
       public func addDelegate(delegate: AnyObject) {
           if !(delegates as NSArray).containsObject(delegate) {
               delegates.append(delegate)
           }
       }
    
       public func removeDelegate(delegate: AnyObject) {
           if (delegates as NSArray).containsObject(delegate) {
               delegates.removeAtIndex((delegates as NSArray).indexOfObject(delegate))
           }
       }
    
       func notifyDelegates()
       {
           for delegate in delegates {
               if delegate.respondsToSelector("watchDataDidUpdate") {
                   let validDelegate = delegate as! WatchDataProviderDelegate
                   validDelegate.watchDataDidUpdate()
               }
           }
       }    
    }
    
    
    // MARK: - Watch Glance (or any view controller) listening for changes
    
    class GlanceController: WKInterfaceController, WatchDataProviderDelegate {
    
        // A var in Swift is strong by default
        var dataProvider = WatchDataProvider.sharedInstance()
        // Obj-C would be: @property (nonatomic, string) WatchDataProvider *dataProvider
    
        override func awakeWithContext(context: AnyObject?) {
            super.awakeWithContext(context)
            dataProvider.addDelegate(self)
        }
    
        // WatchDataProviderDelegate
        func watchDataDidUpdate() {
            dispatch_async(dispatch_get_main_queue(), {
                // update UI on main thread
            })
        }}
    }
    
    class AnyOtherClass: UIViewController, WatchDataProviderDelegate {
    
        func viewDidLoad() {
            WatchDataProvider.sharedInstance().addDelegate(self)
        }
    
        // WatchDataProviderDelegate
        func watchDataDidUpdate() {
            dispatch_async(dispatch_get_main_queue(), {
                // update UI on main thread
            })
        }}
    }
    

    【讨论】:

    • 如果addDelegate() 确实已经存在于delegates 数组中,是否应该只附加delegate
    【解决方案3】:

    在 View-Controller 中进行会话管理(但是 WCSession 是单例的)闻起来像违反 MVC(我已经看到太多的 Watch 博客文章以这种方式错误)。

    我在 WCSession 上创建了一个伞式单例类,它首先从 Watch Extension Delegate 强烈引用,以确保它会很快加载并且不会在工作过程中被释放(例如,当一个 View-Controller 在 transferUserInfo 或transferCurrentComplicationUserInfo 发生在另一个监视线程中)。

    然后只有这个类处理/持有 WCSession 并将会话数据(模型)与手表应用程序中的所有视图控制器分离,主要通过公共静态类变量公开数据,提供至少基本级别的线程安全.

    然后这个类被用于复杂控制器、glance 控制器和其他视图控制器。更新在后台运行(或在 backgroundFetchHandler 中),根本不需要任何应用程序(iOS/WatchOS)在前台(如 updateApplicationContext 的情况),并且会话不一定必须是当前可访问的。

    我并不是说这是理想的解决方案,但是一旦我这样做了,它终于开始工作了。我很想听到这是完全错误的,但由于在采用这种方法之前我遇到了很多问题,所以我现在会坚持下去。

    我不是故意给出代码示例,因为它很长,我不希望任何人盲目地复制粘贴它。

    【讨论】:

    • 我看过 Apple WWDC2015 WatchConnectivity 的完整会议。而且我知道他们建议将通信设置为非常早的状态! (应用程序委托)。我可以听从你的建议。但问题是如何将 AppDelegate 中出现的“委托”转发给所有 ViewController。一个非常基本的方法是在所有 ViewController 中安装一个计时器并定期读取公共静态类变量。但我认为只有在调用“-(void)session:”以通过 VC 中的公共静态类变量时,才有更好的方法来触发函数。
    • 也许这个 SO stackoverflow.com/questions/28809226/… (如果 CFNotificationCenter 仍在 watchOS 2 中工作)。并发症使用“拉”方法代替,除了强制刷新(这是值得怀疑的,因为它很容易超过每日限制)。
    • igraczech,您可以发布代码以供您参考吗?我已经使用在 Watch ExtensionDelegate 中调用的 unbrella 单例 WatchSessionManager 类完成了类似的过程,但未在 unbrella 类上触发委托会话接收消息。我不确定我是否正确地保持代理存活,或者它是否过早地释放。
    • 好的,那么,看我上面的帖子。您可以在 deinit {} 方法中放置一个断点/日志,以查看它何时被完全释放。
    • @rocotilos 只是看到上面的帖子开始“嗯,这是简化版......”,不,我不是故意发布完整的代码。这是关于理解,而不是关于 Cmd-C/Cmd-V/Cmd-B/WTF?
    【解决方案4】:

    我通过“尝试和错误”找到了一个解决方案。它正在工作,但我不知道为什么!如果我从 Watch 向 IOS 应用程序发送请求,则 Watch 应用程序的 ViewController 的委托会从 IOS 应用程序的主队列中获取所有数据。我在Watch app的所有ViewControllers的- (void)awakeWithContext:(id)context- (void)willActivate中添加了如下代码:

    以0 ViewController为例:

    [self packageAndSendMessage:@{@"request":@"Yes",@"counter":[NSString stringWithFormat:@"%i",0]}];

    以示例 1 ViewController1:

    [self packageAndSendMessage:@{@"request":@"Yes",@"counter":[NSString stringWithFormat:@"%i",1]}];

    /*
         Helper function - accept Dictionary of values to send them to its phone - using sendMessage - including replay from phone
     */
    -(void)packageAndSendMessage:(NSDictionary*)request
    {
        if(WCSession.isSupported){
    
    
            WCSession* session = WCSession.defaultSession;
            session.delegate = self;
            [session activateSession];
    
            if(session.reachable)
            {
    
                [session sendMessage:request
                        replyHandler:
                 ^(NSDictionary<NSString *,id> * __nonnull replyMessage) {
    
    
                     dispatch_async(dispatch_get_main_queue(), ^{
                         NSLog(@".....replyHandler called --- %@",replyMessage);
    
                         NSDictionary* message = replyMessage;
    
                         NSString* response = message[@"response"];
    
                         [[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess];
    
                         if(response)
                             NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", response);
                         else
                             NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", response);
    
    
                     });
                 }
    
                        errorHandler:^(NSError * __nonnull error) {
    
                            dispatch_async(dispatch_get_main_queue(), ^{
                                NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", error.localizedDescription);
                            });
    
                        }
    
    
                 ];
            }
            else
            {
                NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", @"Session Not reachable");
            }
    
        }
        else
        {
            NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", @"Session Not Supported");
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多