WatchConnectivity:通过Application Context同步最新数据
2015-11-18 09:48
567 查看
译者:钢铁侠般的卿哥(微博)
原文:WatchConnectivity: Sharing The Latest Data via Application Context
关于WatchOS 2还没有掌握的同学可以参考之前已经发布的文章:
WatchOS 2: Hello, World
WatchConnectivity Introduction: Say Goodbye To The Spinner
WatchConnectivity: Say Hello to WCSession
当你的Watch App 只需要展示最新可用的信息时,可通过Application Context传输后台数据。例如,如果你的Glance展示比赛的分数,用户不关心两分钟前4-2的分数,他们只关心当前的分数是4-4。关于交通的APP的例子,用户不关心上上一个五分钟之前开走的巴士,他们只会关心下一辆巴士何时到来。
所以Application Context工作的方式是把分片的数据插入队列,如果在数据被传送前有了一个全新的数据,那么原来的旧数据就会被新数据取代。按照这样的规律直到有新的数据取代。
受到Kristina Thai’s Application Context教程的启发。我将创建一个类似基于emoji的app。在我的app中,用户选择iphone菜单中的食物emoji,然后最新的食物选项会展示在Apple Watch app中:视频
免责声明
注意到在我的app中我将要写比Kristina教程中更多的抽象数据更新模型。所以我演示的demo app会被过度的设计。
假设我即将操作的是你真实的app将会变得比这个大很多。而且它需要更新许多视图或者数据源通过iOSapp(或者Watch app)接收。所以如果你的app正如我演示的那么简单,只有一个视图更新,保持它的简单,然后研究Kristina的教程。
我也正在尝试不同的方式编写抽象层发送更新的数据到app所需的不同部分。所以我相信会有更好的实现方式。如果你有任何想法,请在评论中让我知道你的想法。
步骤
关于这个教程,我假设你已经知道如何在Xcode中创建一个单一视图应用,以及创建一个简单的食物emoji列表视图。如果你对这有疑问,请参考我的 FoodSelectionViewController。
我也假设你了解如何创建一个Watch App同时在Interface.Storyboard中设置基础样式。如果你需要如何设置的帮助,阅读我的WatchOS 2: Hello, World 教程。
最后你应该能够设置基础的单例来封装WCSession,以及在AppDelegate中的application:didFinishLaunchingWithOptions方法和Watch Extension的 ExtensionDelegate 中的applicationDidFinishLaunching方法中激活它。如有疑问请看我的 WatchConnectivity: Say Hello to WCSession教程。
在你的iOS app中有如下代码:
// in your iOS app import WatchConnectivity class WatchSessionManager: NSObject, WCSessionDelegate { static let sharedManager = WatchSessionManager() private override init() { super.init() } private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil private var validSession: WCSession? { // paired - the user has to have their device paired to the watch // watchAppInstalled - the user must have your watch app installed // Note: if the device is paired, but your watch app is not installed // consider prompting the user to install it for a better experience if let session = session where session.paired && session.watchAppInstalled { return session } return nil } func startSession() { session?.delegate = self session?.activateSession() } }在你的Watch App中有如下代码:
// in your WatchKit Extension import WatchConnectivity class WatchSessionManager: NSObject, WCSessionDelegate { static let sharedManager = WatchSessionManager() private override init() { super.init() } private let session: WCSession = WCSession.defaultSession() func startSession() { session.delegate = self session.activateSession() } }当然如果你需要更多的说明,可以参考本教程的源代码。
发送数据
在我的应用中,当用户选择一项食物选项时,它就会被在后台传输到我的Watch App中。这里的iOS应用是发送者,而且这是非常容易实现的。
只需要在你的iOS app中扩展WatchSessionManager单例来更新application context:。
// in your iOS app // MARK: Application Context // use when your app needs only the latest information // if the data was not sent, it will be replaced extension WatchSessionManager { // This is where the magic happens! // Yes, that's it! // Just updateApplicationContext on the session! func updateApplicationContext(applicationContext: [String : AnyObject]) throws { if let session = validSession { do { try session.updateApplicationContext(applicationContext) } catch let error { throw error } } } }现在当用户选择一项食物栏时,你需要调用以下方法:
就是这样!食物项已经在队列中,将会被发送到你的Watch App。除非有一个新的食物选项在发送前被选中。
接收数据
你的Watch App 现在需要接收数据。这也是很容的,只需要实现WCSessionDelegate 中的session:didReceiveApplicationContext: 方法。
// in your Watch App // MARK: Application Context // use when your app needs only the latest information // if the data was not sent, it will be replaced extension WatchSessionManager { // Receiving data func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) { // update data here // this will be filled in in the Updating Data Section below! } }更新数据
现在你接收到了数据,然而这是很麻烦的部分。尝试让你的Watch Extension的InterfaceController和其他的视图或者数据源知道你的数据已经被更新了。一种方法是通过NSNotificationCenter,但是我将要尝试一个不同的方式。这个部分可以通过多种方法实现。同时对于这个app来说有些过度设计。所以记住这些。
自从使用Swift,我的目标是改变模型的类型。不幸的是,在我的博客中关于WCSession的文章提及到,WCSessionDelegate只能以NSOject的子类被实现。所以为了兼容上述条件,我创建了一个可以携带applicationContext数据同时又能转换不可变类型,以及可被多个试图控制器使用的数据源。
// in your WatchKit Extension struct DataSource { let item: Item enum Item { case Food(String) case Unknown } init(data: [String : AnyObject]) { if let foodItem = data["food"] as? String { item = Item.Food(foodItem) } else { item = Item.Unknown } } }现在我设置一个需要更新所有部分并且这些部分需要知道最新更新的数据的协议。
// in your WatchKit Extension // WatchSessionManager.swift protocol DataSourceChangedDelegate { func dataSourceDidUpdate(dataSource: DataSource) }到了有趣的部分!你的WatchSessionManager需要追踪所有的dataSourceChangedDelegate。这需要通过一个数组实现。addDataSourceChangedDelegate方法会从数组中添加代理,方法removeDataSourceChangedDelegate会从数组中删除代理。
// in your WatchKit Extension // WatchSessionManager.swift class WatchSessionManager: NSObject, WCSessionDelegate { static let sharedManager = WatchSessionManager() private override init() { super.init() } private let session: WCSession = WCSession.defaultSession() // keeps track of all the dataSourceChangedDelegates private var dataSourceChangedDelegates = [DataSourceChangedDelegate]() func startSession() { session.delegate = self session.activateSession() } // adds / removes dataSourceChangedDelegates from the array func addDataSourceChangedDelegate(delegate: T) { dataSourceChangedDelegates.append(delegate) } func removeDataSourceChangedDelegate(delegate: T) { for (index, dataSourceDelegate) in dataSourceChangedDelegates.enumerate() { if let dataSourceDelegate = dataSourceDelegate as? T where dataSourceDelegate == delegate { dataSourceChangedDelegates.removeAtIndex(index) break } } } }我们现在可以加入接收application context的实现:
// in your WatchKit Extension // WatchSessionManager.swift // MARK: Application Context // use when your app needs only the latest information // if the data was not sent, it will be replaced extension WatchSessionManager { // Receiver func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) { dispatch_async(dispatch_get_main_queue()) { [weak self] in self?.dataSourceChangedDelegates.forEach { $0.dataSourceDidUpdate(DataSource(data: applicationContext))} } } }现在我们只需要确保我们的InterfaceController是DataSourceChangedDelegate,然后它被WatchSessionManager追踪。
// in your WatchKit Extension // InterfaceController.swift import WatchKit class InterfaceController: WKInterfaceController, DataSourceChangedDelegate { @IBOutlet var foodLabel: WKInterfaceLabel! override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // add InterfaceController as a dataSourceChangedDelegate WatchSessionManager.sharedManager.addDataSourceChangedDelegate(self) } override func didDeactivate() { // remove InterfaceController as a dataSourceChangedDelegate // to prevent memory leaks WatchSessionManager.sharedManager.removeDataSourceChangedDelegate(self) super.didDeactivate() } // MARK: DataSourceUpdatedDelegate // update the food label once the data is changed! func dataSourceDidUpdate(dataSource: DataSource) { switch dataSource.item { case .Food(let foodItem): foodLabel.setText(foodItem) case .Unknown: foodLabel.setText("ˉ\\_(ツ)_/ˉ") } } }大功告成!
你可以查看Github上的全部源代码。
特别感谢@allonsykraken对代码进行审核,以及提供建议。
相关文章推荐
- android接入即时IM(接入亲加通信云)
- Chinco(摩客串串)——App原型交互演示利器
- iOS开发60分钟入门
- android studio学习(一)
- android多媒体编程--复制图片
- iOS App性能优化
- iOS性能优化
- 欢迎关注微信公众号——风色年代
- Android应用程序与SurfaceFlinger服务的连接过程分析
- android游戏开发初学之SurfaceView绘制图片
- Android开发学习笔记之通过API接口将LaTex数学函数表达式转化为图片形式
- iOS视图控制器的整体概述
- iOS高级教程:处理1000张图片的内存优化
- Objective-C基础-对象和方法之间的联系
- Android4.4深入浅出之SurfaceFlinger总体结构
- webview的物理返回键和顶部返回键的写法
- Android左右图片切换(可自动)
- IOS 内存优化和调试技巧
- Android 4.4(KitKat)中的设计模式-Graphics子系统
- iOS应用性能调优的25个建议和技巧