将 WCSession 与多个 ViewController 一起使用

Using WCSession with more than one ViewController

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

任何人都可以在 Objective C 中给出最后一个示例 什么是将 WCSession 与 IOS 应用程序和 Watch 应用程序 (WatchOS2) 一起使用的最佳实践不止一个ViewController.

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

1.) 在 AppDelegate 的 parent (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];

别人说的是不同的队列? "It is the client's responsibility to dispatch to another queue if necessary. Dispatch back to the main."

我完全糊涂了。因此,请举例说明如何将 WCSession 与一个 IOS 应用程序和一个具有多个 ViewController.

的 WatchOS2 应用程序一起使用

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

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

以例子0 ViewController:

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

以例1为例ViewController1:

[自己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");
    }

}

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

Phone:

我相信 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。

XYZ控制器1:

#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 架构要复杂得多,但它仍然基于这个逻辑。

如果您仍有任何问题,我们可能会在聊天中进行进一步讨论。

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

我在 WCSession 上创建了一个伞形单例 [​​=24=],它首先从 Watch Extension Delegate 中强烈引用,以确保它会很快加载并且不会在工作中间被释放(例如,当一个 View -控制器消失,而 transferUserInfo 或 transferCurrentComplicationUserInfo 发生在另一个监视线程中)。

只有这个 class 然后 handles/holds WCSession 并将会话数据(模型)与手表应用程序中的所有视图控制器分离,主要通过 public 公开数据static class 变量至少提供基本级别的线程安全。

然后这个class被复杂控制器、浏览控制器和其他视图控制器使用。在后台(或在 backgroundFetchHandler 中)更新 运行,none 的应用程序 (iOS/WatchOS) 需要在前台(如 updateApplicationContext 的情况)并且会话不当前必须可以访问。

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

我没有刻意给出代码示例,因为它很长,我不想让任何人盲目地复制粘贴它。

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

本质上,任何 WatchDataProviderDelegate 都可以连接到数据提供程序 class,因为它为委托提供数组持有者(而不是一个弱变量)。 使用 notifyDelegates() 方法将传入的 WCSessionData 转发给所有委托。

// 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
        })
    }}
}