【WatchOS 2教程系列三】WatchConnectivity学习之WCSession

原文链接 = http://natashatherobot.com/watchconnectivity-say-hello-to-wcsession/
作者 = Natasha The Robot
原文日期 = 2015-09-21


在读这篇文章之前,请检查一下你是否已经学习了之前两篇关于WatchOS 2的文章:

WatchOS 2: Hello, World
WatchConnectivity Introduction: Say Goodbye To The Spinner

WCSession就是WatchConnectivity的魔法。所以让我们赶紧深挖它吧!

WCSession.defaultSession()会返回WCSession的单例,用于iOSWatch app之间的数据传输。但是,在使用WCSession时仍有一些值得注意的地方。

首先,你必须设置一个sessiondelegate并启动它。

默认的session用于两个相应app的通信(例如iOS app和它的原生WatchKit的扩展)。这个session提供发送、接收、追踪状态的方法。

启动一个app时,应该在默认的session上设置一个delegate并启动它。这将允许系统填充状态属性和提供任何优秀的背景传输。—— Apple 文档说明。

所以你的代码应该写成这样:

1
2
3
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()

在这里,我推荐将你的WCSession作为一个单例,这样你就可以在app中随意使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import WatchConnectivity
// Note that the WCSessionDelegate must be an NSObject
// So no, you cannot use the nice Swift struct here!
class WatchSessionManager: NSObject, WCSessionDelegate {
// Instantiate the Singleton
static let sharedManager = WatchSessionManager()
private override init() {
super.init()
}
// Keep a reference for the session,
// which will be used later for sending / receiving data
private let session = WCSession.defaultSession()
// Activate Session
// This needs to be called to activate the session before first use!
func startSession() {
session.delegate = self
session.activateSession()
}
}

所以你可以在AppDelegateapplication:didFinishLaunchingWithOptions方法中启动你的session,并且可以在app的任意地方使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// truncated...
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
// Set up and activate your session early here!
WatchSessionManager.sharedManager.startSession()
return true
}
// truncated...
}

但是启动session是远远不够的。你需要通过WCSession的多重检查机制,使得你的应用不需要做格式化传输数据的额外工作。

检查设备支持

检查iOS设备是否支持session,WatchOS都是支持session的。

如果你有一个普遍适用的app,例如,iPad将不会支持WCSession(因为iPad不能和Watch配对)。因此需要确保在iOS项目中做isSupported()检查:

1
2
3
4
5
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}

这意味着你的WatchSessionManager单例需要适应不支持WCSession的场景(使用选项):

1
2
3
4
5
6
7
8
9
10
11
12
13
// Modification to the WatchSessionManager in the iOS app only
class WatchSessionManager: NSObject, WCSessionDelegate {
// truncated ... see above section
// the session is now an optional, since it might not be supported
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
// starting a session has to now deal with it being an optional
func startSession() {
session?.delegate = self
session?.activateSession()
}

用于 Watch 的 iOS App 状态

如果你从iOS app发送数据到Watch,你需要做一些额外的检查,这样当Watch处于无法接受数据的状态时,你就不会浪费CPU资源去处理用于传输的数据。

配对

显然,为了从iOS设备传输数据到Watch,用户必须有一个Watch并且和iOS设备配对。

安装 Watch app

一个用户可能有一对设备,但是他们可能删除了设备上的Watch App,所以为了数据传输,你需要检查你的应用确实有安装在所配对的Apple Watch上面。

如果用户有一对设备,但是未安装你的app,那么如果用户能够从你的watch app版本中获得好处,那将成为做这个检查和提示用户安装watch app的关键点。

当单例中的session保持工作时,为了让这些检查更加简单,并且能够在应用中随意使用,我喜欢在iOS app中创建一个validSession变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Modification to the WatchSessionManager in the iOS app only
class WatchSessionManager: NSObject, WCSessionDelegate {
// truncated... see above
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
// Add a validSession variable to check that the Watch is paired
// and the Watch App installed to prevent extra computation
// if these conditions are not met.
// This is a computed property, since the user can pair their device and / or
// install your app while using your iOS app, so this can become valid
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
}
// truncated... see above
}

判断并发是否可用

最后,如果你在app中有使用并发,你必须检查并发是否可用。我不会在WatchConnectivity教程中说明过多并发的细节,但是如果你想要知道更多,可以观看超级有用和全面的 WWDC 2015 Creating Complications with ClockKit session

sessionWatchStateDidChange

为了以防你的iOS app需要WCSession状态变化的信息,这里有一个delegate方法,专门用于通知WCSession的状态变化:

1
2
3
4
/** Called when any of the Watch state properties change */
func sessionWatchStateDidChange(session: WCSession) {
// handle state change here
}

例如,如果你的app需要安装Watch App,你可以实现这个delegate方法,然后去检测你的Watch App是否真正安装了,并且让用户根据iOS app中的添加流顺序去完成最终的安装。

检查设备可达状态

为了正确在iOSWatch使用Interactive Messaging传输数据,你需要做一些额外的工作以确保两个app处于可达状态:

Watch app上的可达能力需要所配对的iOS设备在重启后至少已经解锁一次了。这个属性能够用于决定iOS设备是否需要被解锁。如果reachable设为NO,可能是由于设备重启过,需要解锁。如果处于这种状态,Watch将会展示一个提示框建议用户去解锁他们配对的iOS设备。

在使用Interactive Messaging时,我喜欢增加一个额外的valideReachableSession变量到我的单例中:

1
2
3
4
5
6
7
8
9
10
11
12
// MARK: Interactive Messaging
extension WatchSessionManager {
// Live messaging! App has to be reachable
private var validReachableSession: WCSession? {
// check for validSession on iOS only (see above)
// in your Watch App, you can just do an if session.reachable check
if let session = validSession where session.reachable {
return session
}
return nil
}

如果session是不可达的,你可以如Apple所建议的那样,提示用户去解锁他们的iOS设备。为了知道用户解锁了他们的设备,实现sessionReachabilityDidChangedelegate方法:

1
2
3
4
5
6
7
8
func sessionReachabilityDidChange(session: WCSession) {
// handle session reachability change
if session.reachable {
// great! continue on with Interactive Messaging
} else {
// 😥 prompt the user to unlock their iOS device
}
}

以上!现在你应该已经知道了WCSession的一些要领,所以我们将会学习更加好玩的部分 —— 真正使用它在iOSWatch之间接收和发送收据!

你可以在Github查看完整的WatchSessionManager单例