Communicator
简介
在watchOS和iOS应用之间发送消息和数据的可能性得益于Apple对WatchConnectivity
的工作,然而有很多要处理的代理回调,一些API调用相当类似,并且并不清楚哪些需要和目的。
Communicator试图澄清这一切,为您处理许多屎事,并且非常容易使用。
Communicator支持开箱即用的手表切换,使用闭包而不是代理函数,并允许您的应用程序的多个地方对消息和事件做出反应。
快速入门
每个应用都有自己的共享Communicator
对象来使用,该对象处理所有的底层会话
Communicator.shared
两个平台之间的使用基本上是相同的。
以下是使用Communicator发送简单消息的示例
let message = ImmediateMessage(identifier: "1234", content: ["messageKey" : "This is some message content!"])
Communicator.shared.send(message)
这将会尝试立即向对应方发送消息。如果接收应用无法适当访问,消息发送将失败,但是您可以随时查询此状态。
switch Communicator.shared.currentReachability {
case .immediateMessaging: Communicator.shared.send(message)
default: break
}
在其他设备上,您可以在应用启动周期尽可能早地注册为新消息的观察者。
ImmediateMessage.observe { message in
guard message.identifier == "1234" else { return }
print("Message received!", message)
}
您可以在应用的任何位置观察这些消息,并筛选掉您不关心的消息。任何可在 Communicator
中改变或接收的内容,包括 Reachability
和 WatchState
,都可以使用相同的语法进行观察,只需在您想观察的类型上调用 observe
即可。
Reachability.observe { reachability in
print("Reachability changed!", reachability)
}
另外,您可以在任何时候取消观察。
let observation = Reachability.observe { _ in }
/// ...
Reachability.unobserve(observation)
Communicator
还可以传输 GuaranteedMessage
、数据 Blob
和同步 Context
。
GuaranteedMessage
与 ImmediateMessage
和 InteractiveImmediateMessage
类似,因为它们都有一个标识符,但它们不支持回复处理器,可以在至少为 .backgroundOnly
的可达性状态下发送,并且在传输过程中即使应用被终止也会继续传输。
Blob
是发送大量数据的理想选择(WatchConnectivity
将拒绝在其他消息类型中的大量数据),可以在至少为 .backgroundOnly
的可达性状态下发送,并且在传输过程中即使应用被终止也会继续传输。
您可以使用一个 Context
来在设备间保持同步,这使得它非常适合偏好设置。 Context
不适用于消息传递或发送大量数据。发送或接收一个 Context
将覆盖任何之前发送的 Context
,您可以通过 Communicator.shared.mostRecentlySentContext
和 Communicator.shared.mostRecentlyReceivedContext
在任何时候查询。
最后,您可以通过传输一个 ComplicationInfo
从您的 iOS 应用更新您的 watchOS 配件。您每天可以传输有限数量的 ComplicationInfo
,您可以通过获取 currentWatchState
对象轻松查询可用的传输数量。
如果您有可用的传输,您的 watch 应用将在后台唤醒以处理 ComplicationInfo
。
注意: 您的应用必须在用户的 活动 表盘上添加一个配件,才能在后台唤醒您的手表,并且可用的传输数量必须不为 0。
使用方法
Communicator
每个应用都有自己的共享 Communicator
对象,它应该使用该对象来与对应应用通信。
Communicator.shared
iOS 和 watchOS 之间的 API 几乎完全相同。
第一次访问 `.shared` 实例时,Communicator
将执行所需的操作以激活底层会话并报告收到的消息/数据等信息。
这意味着您应该尽早在应用生命周期中访问共享实例,同时也要尽早观察任何变化,以避免数据丢失。
Reachability.observe { reachability in
// Handle reachability change
}
ImmediateMessage.observe { message in
// Handle immediate message
}
GuaranteedMessage.observe { message in
// Handle guaranteed message
}
注意: 观察任何类型都将隐式访问 `.shared` 实例,因此您只需要观察内容使
Communicator
激活底层会话。
查询当前可达性
在发送任何消息或数据之前,您应该检查对方应用的当前可达性。这可能会随着用户切换智能手表、安装您的应用或使您的应用进入后台而变化。
此外,自 watchOS 6 以来,用户可以在不安装 iOS 应用的情况下安装手表应用,通讯器已考虑这一点。
您可以在任何时候查询当前可达性。
let reachability = Communicator.shared.currentReachability
您还可以观察并响应当前可达性的变化。
Reachability.observe { reachability in
// Handle reachability change
}
不同类型的通信需要不同的最低可达性级别。例如,ImmediateMessage
和 InteractiveImmediateMessage
需要 .immediatelyReachable
,但 GuaranteedMessage
、Blob
、Context
和 ComplicationInfo
至少需要 .backgroundOnly
(尽管可以在 .immediatelyReachable
时发送)。
查询当前激活状态
您可以在任何时候查询通讯器的当前激活状态。
let state = Communicator.shared.currentState
您还可以观察状态变化。
Communicator.State.observe { state in
// Handle new state
}
状态可能会随着用户切换智能手表而变化。通常,您不需要使用此状态,而是应该查询可达性,考虑当前对应用是否已安装。
查询对方设备当前状态
您可以在任何时候查询配对用户的智能手表状态。
let watchState = Communicator.shared.currentWatchState
您还可以观察状态变化。
WatchState.observe { state in
// Handle new state
}
手表状态提供了有关手表是否配对、您的应用是否已安装、是否已在活动表盘上添加复杂的表盘以及其他信息。
此外,您还可以从手表OS应用中查询 iPhone 的状态,因为自 iOS 6 用户可以安装您的手表应用而不安装 iOS 应用。
let phoneState = Communicator.shared.currentPhoneState
与其他所有状态一样,您还可以观察变化。
PhoneState.observe { state in
// Handle new state
}
ImmediateMessage
ImmediateMessage
是一个简单的对象,包含您选择的标识符字符串和 JSON 字典作为内容。
JSON字典的键必须是字符串,值必须是plist类型的。这意味着你可以保存到UserDefaults
中的任何内容;String
、Int
、Data
等。你不能使用ImmediateMessage
在设备之间发送大量数据,因为系统会拒绝它。相反,使用Blob
来发送大量数据。
这就是创建一个简单的ImmediateMessage
的方式
let content: Content = ["TotalDistanceTravelled" : 10000.00]
let message = ImmediateMessage(identifier: "JourneyComplete", content: json)
这就是如何发送它
Communicator.shared.send(message) { error in
// Handle error
}
这对于在两个设备之间进行快速、交互式的通信效果很好,但仅限于少量数据,如果在通信过程中任一设备不可达,则会失败。
如果你从watchOS发送它,如果需要,系统也会将你的iOS应用唤醒,前提是当前的Reachability
是.immediatelyReachable
。
在接收设备上监听新消息
ImmediateMessage.observe { message in
if message.identifier == "JourneyComplete" {
// Handle message
}
}
注意:
Communicator.currentReachability
的值必须是.immediatelyReachable
,否则在发送信息时会出现错误,你可以通过在发送信息时分配错误处理器来捕获错误。
InteractiveImmediateMessage
与常规的ImmediateMessage
相似,但InteractiveImmediateMessage
还接受一个回复处理器,该处理器你必须在接收设备上执行。一旦你在接收设备上执行了处理器,系统就会在发送设备上调用它。
这提供了一个设备之间进行极其快速通信的方式,但和ImmediateMessage
一样,发送和回复时的可连接性必须为.immediatelyReachable
。
在发送设备上发送消息
let message = InteractiveImmediateMessage(identifier: "message", content: ["hello": "world"])
Communicator.shared.send(message) { error in
}
在接收设备上监听消息并执行回复处理器
InteractiveImmediateMessage.observe { message in
guard message.identifier == "message" else { return }
let replyMessage = ImmediateMessage("identifier", content: ["reply": "message"])
message.reply(replyMessage)
}
和ImmediateMessage
一样,如果你从你的手表应用发送它,如果需要,系统也会将你的iOS应用唤醒,前提是当前的可连接性是.immediatelyReachable
。
GuaranteedMessage
你也可以选择使用“保证”方法发送消息。与“ImmediateMessage”不同,“GuaranteedMessage”没有回复处理器,因为消息可以在接收设备不在接收消息时排队,这意味着它们会排队直到会话的下一个创建。
let content: Content = ["CaloriesBurnt" : 400.00]
let message = GuaranteedMessage(identifier: "WorkoutComplete", content: content)
Communicator.shared.send(message) { result in
// Handle success or failure
}
由于消息被排队,因此接收设备在能够处理时可以以流的形式接收消息。你应该尽快设置你的观察器,以避免丢失任何消息,即在您的AppDelegate
或ExtensionDelegate
中。
GuaranteedMessage.observe { message in
if message.identifier == "CaloriesBurnt" {
let content = message.content
// Handle message
}
}
注意:在watchOS中,当在后台时接收到一个
GuaranteedMessage
可能会使系统生成一个WKWatchConnectivityRefreshBackgroundTask
。如果您将这个任务分配给Communicator
的task
属性,Communicator
将在正确的时间自动帮您完成任务的结束。
Communicator.currentReachability
的值不得为.notReachable
,否则会引发错误。
Blob
Blob
与GuaranteedMessage
非常相似,但更适合发送较大的数据块。一个Blob
使用一个identifier
创建,但与分配一个包含JSON字典的内容不同,您应分配纯净的Data
。
这就是如何创建一个Blob
的方法
let largeData: Data = getJourneyHistoryData()
let blob = Blob(identifier: "JourneyHistory", content: largeData)
以及将信息传输到另一台设备的方法
Communicator.shared.transfer(blob: blob) { result in
// Handle success or failure
}
由于Blob
可能比Message
大得多,发送它可能需要更长的时间。系统会处理这个问题,即使在发送设备在完成之前变得无法访问,系统也会继续发送。
在接收设备上,您可以监听新的Blob
。由于这些Blob
通常会在会话重新开始之前排队等待,因此Communicator
通常会非常早地通知观察者。因此,最好是尽快开始监听Blob
,即在AppDelegate
或ExtensionDelegate
中。
Blob.observe { blob in
if blob.identifier == "JourneyHistory" {
let JourneyHistoryData: Data = blob.content
// ... do something with the data ... //
}
}
此外,您还可以在创建Blob
时附加一些元数据,通过传递一个包含plist值的字典来创建。
let metadata = ["DateGenerated": Date()]
let blobWithMetadata = Blob(identifier: "JourneyHistory", content: data, metadata: metadata)
然后,在接收设备上,您可以查询接收到的Blob
的元数据
Blob.observe { blob in
print(blob.metadata)
}
注意:在watchOS中,当在后台时接收到一个
Blob
可能会使系统生成一个WKWatchConnectivityRefreshBackgroundTask
。如果您将这个任务分配给Communicator
的task
属性,Communicator
将在正确的时间自动帮您完成任务的结束。
Communicator.currentReachability
的值不得为.notReachable
,否则会引发错误。
Context
Context
是一个非常轻量级的对象。任何设备都可以发送和接收Context
,系统存储您可以在任何时候查询的最后发送/接收的Context
。这使得它在同步设备间轻量级事物如偏好设置方面非常理想。
Context
没有标识符,它只包含一个JSON字典作为内容。与ImmediateMessage
一样,此内容必须是基本类型,如String
、Int
、Data
等,且不能太大,否则系统将拒绝它。
let content: Content = ["ShowTotalDistance" : true]
let context = Context(content: content)
do {
try Communicator.shared.sync(context)
} catch {
// Handle error
}
您也可以查询任一设备上发送的最后Context
let context = Communicator.shared.mostRecentlySentContext
在接收设备上,您可以监听新的Context
Content.observe { context in
if let shouldShowTotalDistance = context.content["ShowTotalDistance"] as? Bool {
print("Show total distance setting changed: \(shouldShowTotalDistance)")
}
}
您也可以查询任一设备上接收的最后Context
let context = Communicator.shared.mostRecentlyReceivedContext
注意:在watchOS上,当应用处于后台状态时接收一个
Context
可能会触发系统生成一个WKWatchConnectivityRefreshBackgroundTask
。如果您将其分配给Communicator
的task
属性,Communicator
将会在适当的时间自动为您结束任务。
Communicator.currentReachability
的值不能为.notReachable
,否则会抛出错误。
WatchState
WatchState
是Communicator
中的一个仅在iOS上存在的元素。它提供了有关用户配对手表当前状态的某些信息,例如是否启用并发症或是否已安装手表应用。
您可以在iOS上观察WatchState
的任何变化。
WatchState.observe { state in
// Handle watch state
}
您还可以在任何时候从iOS的Communicator
查询当前的WatchState
。
let watchState = Communicator.shared.currentWatchState
您可以使用WatchState
检索指向当前配对手表在iOS设备上特定目录的URL。
您可以使用此目录来存储仅与该手表相关的特定内容,您不希望将其与用户的其它手表关联。如果用户卸载您的watchOS应用或取消手表配对,系统将自动删除此目录(及其包含的任何内容)。
PhoneState
PhoneState
类似于WatchState
,但查询是从手表端进行的。
自watchOS 6以来,用户可以安装手表应用而无需安装iOS应用,您可以使用PhoneState
来确定这一点。
ComplicationInfo
ComplicationInfo
只能从iOS设备发送,并且只能在watchOS设备上接收。它的目的是唤醒后台的watchOS应用以处理数据和更新其并发症。截至撰写本文时,您的iOS应用每天可以这样做50次,您可以通过查询iOS上的共享Communicator
对象的currentWatchState
来找出您还有多少剩余更新。
与Context
一样,ComplicationInfo
没有标识符,其内容是一个JSON字典。
let content: Content = ["NumberOfStepsWalked" : 1000]
let complicationInfo = ComplicationInfo(content: content)
您可以通过以下方式从iOS应用中发送它
Communicator.shared.transfer(complicationInfo) { result in
// Handle success or failure
}
成功传输后,result
中的success
情况提供了当天的剩余并发症更新次数。
在 watchOS 端,你可以观察到新的 ComplicationInfo
正在被接收。就像可能在后台发生的其他传输一样,及早观察这些是有好处的,比如在 ExtensionDelegate
中进行观察。
ComplicationInfo.observe { complicationInfo in
// Handle update
}
Communicator.currentReachability
的值不能为.notReachable
,否则会抛出错误。
注意:在后台接收
ComplicationInfo
可以导致系统生成WKWatchConnectivityRefreshBackgroundTask
。如果您将其分配给Communicator
的task
属性,Communicator
将会自动在正确的时间帮您结束任务。
示例
要运行示例项目,首先克隆仓库,从 Example 目录中运行 pod install
。
watchOS 和 iOS 示例应用设置了新 Message
、Blob
、可达性变化等的观察者,并将任何更改打印到控制台。它们在应用程序早期就设置这些观察者,这建议用于状态变化和观察那些可能在应用程序终止期间已转移的东西的观察者,比如 Blob
。
尝试运行每个目标,当与按钮交互时查看输出。
要求
Communicator
依赖于 WatchConnectivity
,苹果公司用于在 iOS 和 watchOS 应用之间通信的框架,但它没有外部依赖。
Communicator
需要 iOS 10.0 及更高版本和 watchOS 3.0 及更高版本。
安装
Swift 包管理器
Communicator
支持使用 SPM,只需在 Xcode 11 或更高版本中将 Communicator
作为包依赖项添加即可。
CocoaPods
在您的 Podfile 中添加以下行,然后在终端中运行 pod install
。
pod "Communicator"
作者
凯恩·切舍尔,@kanecheshire
许可协议
Communicator可在MIT许可协议下使用。有关更多信息,请参阅LICENSE文件。