您的位置:首页 > 移动开发

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对代码进行审核,以及提供建议。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: