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唤醒时,会触发回调之前接收到的所有数据。

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

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

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

参考资料

Watch Connectivity 官方文档

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容