Apple Watch开发-WatchConnectivity与iPhone间的通信和数据共享

传送门

下载官方Demo

WatchConnectivity框架

WatchConnectivity框架可在iOS应用和配对watchOS应用的 WatchKit Extension 之间传输数据。还可以使用此框架来触发watchOS应用程序的Complication(复杂性的表盘小元素)的更新。

WCSession

iOS App 和 Watch App 在运行期间必须同时创建和配置WCSession的实例,当两个会话对象都处于活动状态时,两个进程可以通过来回发送消息来立即进行通信。当只有一个会话处于活动状态时,活动的会话可能仍会发送更新并传输文件,但是这些传输是在后台时机进行的。

注意:在尝试发送消息或获取有关连接状态的信息之前,必须配置并激活会话对象。在激活会话之前,可以调用isSupported()方法以确保当前设备可以使用Watch Connectivity框架。

要配置和激活会话,分配一个代理给默认的会话对象并调用该对象的activate()方法。WatchKit extension和iOS App必须分别配置自己的会话对象。激活会话将在两个应用程序之间建立连接。(iOS设备使用WCSession实例前必须检查是否支持WCSession,而watchOS始终是支持WCSession的,则不需要检查)

func configureSession() {
    // iOS设备使用WCSession实例前必须检查是否支持WCSession,而watchOS始终是支持WCSession的,则不需要检查
    if WCSession.isSupported() {
        let session = WCSession.default
        session.delegate = self
        session.activate()
    }
}

WCSessionDelegate

WCSession对象用于在WatchKit extension和配对的活动的iOS App之间进行通信。配置WCSession对象时,必须指定一个实现WCSessionDelegate协议的代理对象。
该协议的大多数方法都是可选的,但是,应用程序必须实现session(_:activationDidCompleteWith:error:)方法,以支持异步激活。

注意:
该协议的方法均在App的后台线程上调用的,因此在这些方法中如果要对UI界面进行操作,必须回到主线程操作。

一台运行iOS 9.3或更高版本的iPhone可以与多个运行watchOS 2.2或更高版本的Apple Watch配对。实现以下方法:

  • session(_:activationDidCompleteWith:error:)
  • sessionDidBecomeInactive(_:) (仅适用于iOS)
  • sessionDidDeactivate(_:) (仅适用于iOS)

与iPhone间的通信方式

仅当activationState属性为WCSessionActivationState.activated时,才可以启动向对应App的数据传输。iOS App 在尝试传输之前应确保已经安装Watch App。

  • 前台传输:当两个应用程序都处于活动状态(active)时,可以通过WatchConnectivity框架进行实时通信。
  • 后台传输:若接收的App处于非活动状态(inactive)时,大多数传输都在后台进行,且当接收的App唤醒时,会触发回调之前接收到的所有数据。
Apple Watch开发-WatchConnectivity与iPhone间的通信和数据共享_第1张图片

1) 后台传输-更新应用程序上下文 updateApplicationContext(_:)

func updateApplicationContext(_ applicationContext: [String : Any]) throws

使用此方法可以将Dictionary传输到相应的应用程序。系统在适当的时机发送上下文数据,目的是在对方唤醒之前准备好使用数据。对方的会话将数据传递到代理方法session(_:didReceiveApplicationContext:)。对方也可以从其WCSessionreceivedApplicationContext这个属性中获取索数据。

此方法会替换了之前设置的词典,因此可以使用此方法来传达状态更改或传递经常更新的数据。例如,此方法非常适合更新应用程序的外观。

isReachablefalse时,适合调用此方法。

2) 实时传输
发送数据字典:sendMessage(_:replyHandler:errorHandler:)
发送数据对象:sendMessageData(_:replyHandler:errorHandler:)

func sendMessage(_ message: [String : Any], 
    replyHandler: (([String : Any]) -> Void)?, 
    errorHandler: ((Error) -> Void)? = nil)

func sendMessageData(_ data: Data, 
        replyHandler: ((Data) -> Void)?, 
        errorHandler: ((Error) -> Void)? = nil)

立即将消息发送到已配对且处于活动状态的设备,并有选择地处理响应。消息按顺序排队,并按发送顺序传递。消息的传递是异步发生的。

如果指定了replyHandler,则该block会在后台线程上异步执行。

WatchKit Extension处于activated状态且正在运行时从其调用此方法,它将在后台唤醒相应的iOS App并使其变为reachable状态
相反,从iOS App调用此方法则不会唤醒WatchKit Extension

如果调用此方法,并且对方是unreachable状态(或在传递消息之前变得unreachable),则执行errorHandler并提供适当的错误。如果参数包含非属性列表(property list)数据类型,则也可以调用该块。

3) 后台传输-传输数据字典 transferUserInfo(_:)

func transferUserInfo(_ userInfo: [String : Any] = [:]) -> WCSessionUserInfoTransfer

调用此方法可以将数据字典传输给对方,传输成功或发生错误,都会代理方法session(_:didFinish:error:)
使用此方法发送的字典会在另一台设备上排队,并按其发送顺序进行传递。转移开始后,即使应用已暂停,转移操作也会继续。

注意:
始终在配对设备上测试Watch Connectivity数据传输。模拟器上的App不支持transferUserInfo(_:)方法。

4) 后台传输-传输文件 transferFile(_:metadata:)

func transferFile(_ file: URL, 
         metadata: [String : Any]?) -> WCSessionFileTransfer

使用此方法可以发送当前设备本地的文件。文件在后台线程上异步传输到对方。
系统尝试尽快发送文件,但可能会限制传输速度以适应性能和功耗方面的考虑。
通过属性outstandingFileTransfers获取已排队等待传递但尚未传递给对应文件的文件的列表。

如果无法发送文件及其随附的字典,则会话对象将调用代理方法session(_:didFinish:error:)方法并报告错误。
如果词典包含非属性列表对象类型,或者指定的URL不包含有效文件,则可能会发生错误。

注意:
始终在配对设备上测试Watch Connectivity数据传输。模拟器上的App不支持transferFile(_:metadata:)方法。

5) 后台传输-传输当前Complication信息 transferCurrentComplicationUserInfo(_:)

func transferCurrentComplicationUserInfo(_ userInfo: [String : Any] = [:]) -> WCSessionUserInfoTransfer

有新数据要发送给Complication时,请调用此方法。WatchKit Extension可以使用这些数据替换或扩展其当前的时间轴条目。

如果对方App允许Complication,系统会尝试立即传输此userInfo。对方收到此userInfo后,系统将在后台启动Watch App Extension并允许它更新Complication的内容。如果当前userInfo无法传输(即社诶已经断开,超出后台启动预算等),它将在outstandingUserInfoTransfers队列中等待,直到下一个合适的时机。outstandingUserInfoTransfers队列只能存在一个当前的ComplicationuserInfo,如果当前的ComplicationuserInfo尚未解决(等待传输),并且再次调用-transferCurrentComplicationUserInfo:传递了新的userInfo,则新userInfo将被标记为isCurrentComplicationInfo,而先前的userInfo将被取消标记。

PS:
该api的注释最后写道:The previous user info will however stay in the queue of outstanding transfers.
意思是:先前的userInfo将一直存在于outstandingUserInfoTransfers队列中。
但实践证明,outstandingUserInfoTransfers队列中的userInfo会逐个回调给对方App,只不过你可以在回调中判断isCurrentComplicationInfo属性,来确定哪个才是当前的Complication信息。

独立 Watch App 向 iOS App 传输信息报错

在实践中发现,当 Watch App 支持独立运行(在WatchKit App Extension-General-勾选Supports Running Without iOS App Installation)时,Watch App 向 iOS App 传输信息时,总会报错:

Error Domain=WCErrorDomain Code=7018 "Companion app is not installed." 
UserInfo={NSLocalizedDescription=Companion app is not installed., 
NSLocalizedRecoverySuggestion=Install the Companion app.}

同样,调用WCSession.default.isCompanionAppInstalled也总是返回false,而我确实安装了配套的iOS App。

显然这不是我们期望的,我们希望 Watch App 支持独立运行的同时,如果安装了配套的iOS App,也能支持 Watch App 通过WatchConnectivity 向 iOS App 传输信息。

不知道苹果是出于什么考虑,亦或者这是苹果的bug?

网上有人提出同样的问题:WatchOS应用未检测到同伴iOS应用(WatchOS app not detecting companion iOS app)

若Watch App支持独立运行,勾选Supports Running Without iOS App Installation

使用App Groups数据共享

App Groups允许单个开发团队生产的多个应用程序访问共享容器并使用进程间通信(IPC)进行通信。App可以属于一个或多个App Groups。

启用App Groups

  1. 选中Target-Signing & Capabilities-添加App Groups项-添加identifier,这将会在指定的Target中自动添加一个.entitlements文件,并将该identifier添加到文件中。
  2. 同样在WatchKit AppWatchKit Extension中添加相同的App Group identifier
Apple Watch开发-WatchConnectivity与iPhone间的通信和数据共享_第2张图片

使用App Groups
可以通过UserDefaultsinit?(suiteName suitename: String?)获取共享UserDefaults读写基本数据。

通过FileManager
containerURL(forSecurityApplicationGroupIdentifier groupIdentifier: String) -> URL?在共享文件目录中读写文件数据。如果使用无效的App Group Identifier调用该方法,将返回nil

参考资料

Watch Connectivity 官方文档

WatchOS开发教程之四: Watch与 iPhone的通信和数据共享

你可能感兴趣的:(Apple Watch开发-WatchConnectivity与iPhone间的通信和数据共享)