WatchOS开发教程之四: Watch与 iPhone的通信和数据共享
Posted DCSnail-蜗牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WatchOS开发教程之四: Watch与 iPhone的通信和数据共享相关的知识,希望对你有一定的参考价值。
WatchOS 开发教程系列文章:
WatchOS开发教程之一: Watch App架构及生命周期
WatchOS开发教程之二: 布局适配和系统Icon设计尺寸
WatchOS开发教程之三: 导航方式和控件详解
WatchOS开发教程之四: Watch与 iPhone的通信和数据共享
WatchOS开发教程之五: 通知功能开发
WatchOS开发教程之六: 表盘功能开发
Apple Watch与iPhone之间的通信
在第一篇文章Watch App架构及生命周期的最后, 我们提到过: 避免长时间运行的任务。由于与 Watch App的交互通常很简短, 因此在长时间运行的任务完成之前, 可能会暂停 WatchKit Extension。 执行任何长时间运行任务的最佳解决方案是在 ios App中执行该任务, 然后将数据传输给 Apple Watch。
所以, 今天来说一说Apple Watch与iPhone之间的通信是如何实现的。
WatchConnectivity框架
在 WatchOS中有个WatchConnectivity框架
, 是专门负责 WatchOS与 iOS之间的通信的。使用Connectivity框架
在 WatchKit Extension和 iOS App之间进行通信。该框架提供了两个进程之间的双向通信,并允许在前台或后台进行数据和文件的传输。
Connectivity框架
提供了几种在 iOS 和 WatchKit Extension之间发送数据的选项, 每个选项都用于不同的用途。大多数选项在后台执行单向数据传输,而且是提供更新的便捷方式。前台传输让你的应用立即发送消息并等待回复。
对于大多数类型的传输,您提供一个NSDictionary
包含要发送的数据的对象。字典的键和值必须都是属性列表类型,因为数据必须序列化并以无线方式发送。属性列表类型是指Foundation框架中的NSNumbe
、NSString
、NSArray
、NSDictionary
、Bool
、NSDate
、NSData
等数据类型。如果需要包含非属性列表类型的类型,请将它们打包到NSData
对象中,或者在发送之前将它们写入文件。此外,您发送的词典应该是紧凑的,并且只包含您需要的数据。保持字典较小可确保它们快速传输,并且不会在两台设备上消耗太多电量。
WCSession
WatchConnectivity框架
中主要是通过WCSession
类进行数据传输的。来看下WCSession
这个类, 它有一个default
单例。default session
用于在两个对应应用程序(即 iOS App及 WatchKit Extension)之间进行通信。Session
提供了发送,接收和跟踪状态的方法。
您的 iOS App和 WatchOS App必须在执行期间的某个时刻创建和配置此类的实例。当两个会话对象都处于激活状态时,这两个进程可以通过发送消息立即进行通信。当只有一个Session
处于激活状态时,Session
仍可以发送更新和传输文件,但这些传输在后台机会性地发生。
Session的配置和激活
在尝试发送消息或获取有关连接状态的信息之前,必须配置并激活Session
。 在激活Session
之前,可需要先进行一个检查当前 iOS设备是否支持Connectivity框架
, 方法就是调用isSupported()
方法。
在 iOS App中检查是否支持Connectivity框架
并激活Session
的代码如下:
func configureWCSession()
if #available(iOS 9.0, *)
// Some properties can be checked only for iOS Device
// WCSession.default.isPaired
// WCSession.default.isWatchAppInstalled
// WCSession.default.isComplicationEnabled
if WCSession.isSupported()
let session = WCSession.default
session.delegate = self
session.activate()
else
// Current iOS device dot not support session
else
// The version of system is not available
在WCSession
类中, 还有一些属性是只能在 iOS App中使用的。比如, isPaired
, isWatchAppInstalled
, isComplicationEnabled
, remainingComplicationUserInfoTransfers
, watchDirectoryURL
。这些都是仅仅在 iOS App中可用的, 都是标示当前设备的某些状态的。所以, 我们要在通信前利用好这些属性。
在激活WatchKit Extension的Session
前, 不必检查是否支持Connectivity框架
, 因为 WatchOS一定支持Connectivity框架
。所以, 在 WatchKit Extension中Session
的配置和激活就相对简单一些:
func configureWCSession()
// Don't need to check isSupport state, because session is always available on WatchOS
// if WCSession.isSupported()
let session = WCSession.default
session.delegate = self
session.activate()
选择合适的通信方式
WatchKit Extension与 iPhone间的通信方式有很多种, 可以分为前台实时传输和后台不定时传输两大传输类型。前台传输, 是实时传输, 消息字典传输和消息数据传输。后台传输又分为覆盖式传输, 队列式传输。队列式传输又分为字典传输, 文件传输, 表盘数据传输。一张图把这一切说清楚:
后台传输
后台传输
是异步执行的, 当发送方的应用退出时,后台传输将会继续。对应的接收方应用不需要运行也可以继续进行后台传输。并且在 WatchKit Extension与 iPhone进行传输的方法中, 所有的后台传输都是不定时传输。不定时意味着, 数据不一定会立即传输, 而是系统将在适当的时间传输内容。当然包括上面所说的在应用程序退出之后发生, 甚至是在双方应用都不运行时发生。接收方没有运行但传输成功了, 下次启动时将会触发相应的代理方法。
后台覆盖式传输
后台传输中覆盖式
的传输意味着, 当你进行数据传输时, 如果第一次发送的数据还没有送出去, 在此时进行第二次数据传递, 将会覆盖第一次的数据。这时数据接收方接收的数据只会有第二次的, 第一次的数据会丢失。
在Connectivity框架
的通信方法中, 后台覆盖式传输只有一个方法, 在 Objective-C中就是updateApplicationContext:error:
方法, 在 Swift中函数如下:
open func updateApplicationContext(_ applicationContext: [String : Any]) throws
一般使用该方法将最近的状态信息传递给对方, 且只有在Session
处于激活状态时才能调用此方法, 系统将会在适当的时间传输内容。使用此方法传输后, 发送的数据会存储在applicationContext
属性中, 而最新接收的数据会存储在receivedApplicationContext
属性中。
接收方可以遵从 WCSessionDelegate
的协议, 在这些代理方法中, 有一个是与上面的方法成对存在的。接收方若实现了下面的代理方法, 当数据发送方在调用上面的方法后, 将会触发它们:
optional public func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any])
后台队列式传输
后台传输中队列式
的传输意味着, 后一次的传输不会覆盖前一次所传输的数据。系统会把所有的数据按照次序进行发送。在Connectivity框架
的通信方法中, 后台队列式有三个方法, 后台队列式字典传输, 后台队列式文件传输, 后台队列式表盘数据传输。
后台队列式字典传输
在 Objective-C中后台队列式字典传输的方法是transferUserInfo:
, 在 Swift中函数如下:
open func transferUserInfo(_ userInfo: [String : Any] = [:]) -> WCSessionUserInfoTransfer
此方法可以传输一个字典, 且只有在Session
处于激活状态时才能调用此方法。系统将userInfo
字典按序排入队列, 并在适当的时候将其传输到接收方应用中。你还可以通过outstandingUserInfoTransfers
属性来获取仍在传输中(即未被接收方取消, 失败或已接收)的userInfo
数组。
接收方可以遵从 WCSessionDelegate
的协议, 在这些代理方法中, 有两个是与上面的方法成对存在的。接收方若实现了下面的代理方法, 当数据发送方在调用上面的方法后, 将会触发它们:
optional public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:])
optional public func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?)
后台队列式文件传输
在 Objective-C中后台队列式文件传输的方法是transferFile:metadata:
, 在 Swift中函数如下:
open func transferFile(_ file: URL, metadata: [String : Any]?) -> WCSessionFileTransfer
此方法可以传输一个文件和一个可选字典, 且只有在Session
处于激活状态时才能调用此方法。你还可以通过outstandingFileTransfers
属性来获取仍在传输中(即未被接收方取消, 失败或已接收)的userInfo
数组。
接收方可以遵从 WCSessionDelegate
的协议, 在这些代理方法中, 有两个是与上面的方法成对存在的。接收方若实现了下面的代理方法, 当数据发送方在调用上面的方法后, 将会触发它们:
optional public func session(_ session: WCSession, didReceive file: WCSessionFile)
optional public func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?)
这里需要注意的是, 接收到的文件存放在本地临时路径Documents/Inbox/
中, 代理方法session(_ session: WCSession, didReceive file: WCSessionFile)
结束后系统会将文件删除。所以你需要接收到文件后, 立即对其进行读取或者移动。
后台队列式表盘数据传输
在 Objective-C中后台队列式表盘数据传输的方法是transferCurrentComplicationUserInfo:
, 在 Swift中函数如下:
open func transferCurrentComplicationUserInfo(_ userInfo: [String : Any] = [:]) -> WCSessionUserInfoTransfer
此方法涉及到 WatchOS的表盘功能也就是Complication
功能, 且只适用于 iPhone向 WatchKit Extension发送表盘功能相关的数据。此方法将包含表盘功能的最新信息的字典userInfo
排入队列中。并且只有在Session
处于激活状态时才能调用此方法。
与之相关的属性有remainingComplicationUserInfoTransfers
, 它标示这transferCurrentComplicationUserInfo:
方法的剩余调用次数。在系统开始将表盘userInfo
作为常规userInfo
传输之前。 如果此属性为0,则表盘userInfo
将作为常规userInfo
传输。 当 Watch应用未启用表盘功能时, 其计数也为0。
如果启用了表盘功能, 系统将立即尝试向 WatchKit Extension传输此userInfo
, 且传输为高优先级。一旦收到当前的表盘功能的userInfo
, 系统将在后台启动 WatchKit Extension并允许其更新并表盘内容。如果当前用户信息无法传输(即设备断开连接, 超出后台启动预算等), 它将在outstandingUserInfoTransfers
队列中等待, 直到下一个合适的时间。
需要注意的是, 在outstandingUserInfoTransfers
队列中只能有一个当前的表盘的userInfo
。如果当前表盘userInfo
还在队列当中(等待传输), 并且再次传输了一个新的userInfo
, 则新userInfo
将被标记为当前需要传输的userInfo
。而先前的userInfo
将被取消标记, 那么无论如何它都将一直存在于outstandingUserInfoTransfers
队列中了。
表盘功能传输中接收方(即 Watch端)可以实现的代理与后台队列式字典传输的代理相同。
前台传输
在 WatchKit Extension与 iPhone进行传输的方法中, 前台传输是实时的, 且是队列式的传输方式。具体有两种方法, 一种是传输消息字典, 另一种是传输消息数据。
前台消息字典传输
在 Objective-C中前台消息字典传输的方法是sendMessage:replyHandler:errorHandler:
, 在 Swift中函数如下:
open func sendMessage(_ message: [String : Any], replyHandler: (([String : Any]) -> Swift.Void)?, errorHandler: ((Error) -> Swift.Void)? = nil)
此方法传入一个消息字典, 一个处理接收方回复的block, 以及一个错误处理block。消息的传递是异步的、高优先级的, 且只有在会话处于活动状态时才能调用此方法。如果指定了处理接收方回复的block, 则该block也会在后台线程上异步执行。
需要注意的是, 从 WatchKit Extension激活并运行时调用此方法会在后台唤醒相应的 iOS App并使其可访问。但若从 iOS App调用此方法则不会唤醒相应的WatchKit Extension。如果调用此方法时接收方无法访问(即isReachable
是 false), 则会执行errorHandler
block并显示相应的错误。
那么isReachable
什么时候是true
呢? 对于 WatchKit Extension来说, iOS设备在范围内, 因此可以进行通信并且 WatchKit Extension在前台运行,或者在后台运行时具有高优先级(例如, 在锻炼会话期间或当表盘加载其初始时间轴数据时); 对于 iOS来说, 配对且激活的 Apple Watch在范围内, 相应的WatchKit Extension正在运行。只要这样isReachable
属性才会为true。
而且当传输的消息字典中包含非属性列表数据类型, 也会调用errorHandler
block。其他的类型数据应该用你下面的方法来进行传输。
前台消息数据传输
在 Objective-C中前台消息字典传输的方法是sendMessageData:replyHandler:errorHandler:
, 在 Swift中函数如下:
open func sendMessageData(_ data: Data, replyHandler: ((Data) -> Swift.Void)?, errorHandler: ((Error) -> Swift.Void)? = nil)
此方法与消息字典传输的方法的区别在于所传输的主体内容为Data
类型。包含非属性列表数据类型的传输, 就需要使用此方法了, 否则用上面方法将会报错。
传输数据处理
当你选择了合适的方式进行数据通信后, 就是处理这些接收到的数据和处理接收方回复了。核心就是在WCSessionDelegate
中不同传输方式对应的不同代理方法中去处理。其实, 这些上面每个方法中已经详细说过了, 但为了知识的结构这里还是有必要提一下的, 详细的这里不赘述了。
代码描述
以前台消息字典传输为例, 由 WatchKit Extension向 iOS App发送消息。 WatchKit Extension中代码如下:
if !WCSession.default.isReachable
let action = WKAlertAction(title: "OK", style: .default)
print("OK")
presentAlert(withTitle: "Failed", message: "Apple Watch is not reachable.", preferredStyle: .alert, actions: [action])
return
else
// The counterpart is not available for living messageing
let date = Date(timeIntervalSinceNow: 0.0)
let message = ["title": "Apple send a messge to iPhone", "watchMessage": "The Date is \\(date.description)"]
WCSession.default.sendMessage(message, replyHandler: (replyMessage) in
print(replyMessage)
DispatchQueue.main.sync
self.contentLabel.setText(replyMessage["replyContent"] as? String)
) (error) in
print(error.localizedDescription)
WatchKit Extension中实现对应代理方法, 以处理 iOS App发回的回复数据:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void)
DispatchQueue.main.sync
contentLabel.setText(message["iPhoneMessage"] as? String)
iOS App也应实现对应代理, 以处理接收到的数据:
@available(iOS 9.0, *)
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void)
print(message)
replyHandler(["title": "received successfully", "replyContent": "This is a reply from iPhone"])
数据共享
Watch App和 WatchKit Extension间数据共享
在运行时可以使用共享App Group
在 Watch App和 WatchKit Extension之间共享媒体文件。 App Group
创建一个多个进程可以访问的安全容器。 通常每个进程都在自己的沙箱环境中运行, 但是App Group
允许两个进程共享一个公共目录。
如何使用共享App Group
1.在Xcode中打开项目的Capabilities选项卡。
2.启用App Group
功能。这将会添加一个entitlement file
到指定的Target,并将一个唯一标识的App Group
添加到该文件中。
3.且需要注意Watch App和 WatchKit Extension的 Target必须都启用相同的App Group
。
4.访问其中内容时, 使用NSFileManager
的containerURLForSecurityApplicationGroupIdentifier:
方法取得文件的URL。
文件存储
WatchKit Extension的存储目录与iOS App的存储目录具有相同的基本结构。将用户数据和其他关键信息放在Documents
目录中。如果将文件放在Caches
目录中, 磁盘空间量较低时系统会删除它们。
数据备份
Apple Watch不会自动备份WatchKit Extension保存的文件。如果需要备份 Watch App中的数据, 则必须将该数据明确传输回iOS App并将其保存在那里。
iCloud
从 WatchOS 3开始, WatchKit Extension可以直接与CloudKit
和其他iCloud技术进行通信。
相关资料:
Sharing Data
WatchOS 开发教程源码:Watch-App-Sampler
以上是关于WatchOS开发教程之四: Watch与 iPhone的通信和数据共享的主要内容,如果未能解决你的问题,请参考以下文章