将 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
})
}}
}
我发现了很多问题和答案,但没有请求的最终示例:
任何人都可以在 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
})
}}
}