static var CounterKey = “_counter” ... objc_setAssociatedObject(trackableClass, &RuntimeConstants.CounterKey, counter+1, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
为什么我们使用 var 来修饰计数器的键这个静态属性并在传递到其他地方时使用引用?答案隐藏在 Swift 语言基础——字符串之中。字符串像其他所有的值类型一样,是按值传递的。那么,当你把它传入这个闭包时,这个字符串将会被复制到一个不同的地址,这会导致在关联对象表中产生一个完全不同的键。& 符号总是保证将相同的地址作为键参数的值。你可以尝试以下代码:
你也许注意到了,我们目前还没有实现队列的协议。当我们进行编码时,通常希望事物之间能保持相对分离。并且希望能创建出一个概览从而方便我们去进行查找。有些类可能会逐渐变得非常大,解决这种情况的方法之一是使用扩展作用域。这样,每一个扩展倾向于只做一个任务(比如去遵循一个协议,处理存储与初始化,又或是嵌套类的声明等),事后再去查找时就会容易很多。让我们在这里也尝试使用这种方式。首先,实现一个 Int 类型的私有扩展,这能够帮助我们执行一些预先定义好的索引计算:
privateextensionInt{ var leftChild: Int { return (2 * self) + 1 } var rightChild: Int { return (2 * self) + 2 } var parent: Int { return (self - 1) / 2 } }
这里的内容有点多,你也许会想多读上一两次。其中,最上面是我们先前在协议中所定义好的所有方法,下面则是一些私有的,仅在此类中可用的辅助方法。我已经为这些辅助方法加上了注释,以便你能快速的了解到它们是用来做什么的。此外,记得关注一下先前对 Int 的扩展在这里是如何被使用的。依我看来,这是非常简洁实用的设计。
Bluetooth 商标 - 包括 BLUETOOTH 文字商标,图形商标(符文 B 和椭圆形设计),还有组合商标(蓝牙文字商标和设计)- 这些都被 Bluetooth SIG 所拥有。只有 Bluetooth SIG 的成员并且拥有对应资格和申报过的产品才可以展示,相关功能或者使用任何商标。为了保护这些商标,Bluetooth SIG 管理了一套执行程序,监控市场并进行审核,以确保会员使用商标的行为符合蓝牙品牌指南,并确保最终发布的产品与已通过资格审查程序的商品和服务相对应。
成为 SIG 的一员 会包含很多好处。你可以免费使用教育工具包、培训视频、网络研讨会、开发人员论坛、开发人员支持服务、白皮书、产品测试工具,并帮助确保您的应用程序满足国际监管要求(主要是关于 射频排放)。
你只要成为会员就会得到一些曝光。我的公司是它的一个成员,所以在 Bluetooth SIG’s Member Directory 中可以 被看到:
一旦你开发了一款应用,使其通过 SIG 认证,并获得 Apple App Store 的许可,那么你的产品同时也会被 SIG 公开上市,这时你将获得更多的曝光。
对应用程序进行资格认证既简单又便宜
当你对自己基于 Core Bluetooth 开发的应用程序感到满意,并准备将其提交到 Apple App Store 进行审核,请停下,然后前往 Bluetooth SIG 的网页对你的应用程序进行 认证。SIG 将为您提供一个整洁的 “Launch Studio”,它是您用来完成 Bluetooth Qualification Process 的在线工具。”
对于大多数应用程序,比如我将在本教程中介绍的 “GATT - based Profile Client(app),”认证和上市的费用是 100 美元。花一些精力来确保您的代码符合 Bluetooth 规范和做一些测试,将是非常值得的。最后,可以给你的应用程序印上蓝牙的商标。这个 商标 “在全球范围内都是可识别的,消费者认知度高达92%。”
外围设备以广播包的形式广播一些数据。一个广播包是一个相对较小的数据束,其中可能包含外围设备所能提供的有用信息,比如外围设备的名字还有主要功能。例如,数字恒温器可能会广播它能提供房间的当前温度。在 BLE 中,广播是外围设备展示其存在的主要方式。另一方面,中央设备可以扫描和监听任何外围设备,只要这些设备的广播信息是它感兴趣的……
// MARK: - CBPeripheralDelegate methods funcperipheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { for service in peripheral.services! { if service.uuid == BLE_Heart_Rate_Service_CBUUID { print("Service: \(service)") // STEP 9: 在感兴趣的服务中寻找感兴趣的特征 peripheral.discoverCharacteristics(nil, for: service) } } } // END func peripheral(... didDiscoverServices // STEP 10: 从感兴趣的服务中,确认我们所发现感兴趣的特征 funcperipheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { for characteristic in service.characteristics! { print(characteristic) if characteristic.uuid == BLE_Body_Sensor_Location_Characteristic_CBUUID { // STEP 11: 订阅关于感兴趣特征的单次通知; // “当你使用这个方法去读取特征的值时,外围设备将会调用…… // peripheral:didUpdateValueForCharacteristic:error:” // // Read Mandatory // peripheral.readValue(for: characteristic) }
if characteristic.uuid == BLE_Heart_Rate_Measurement_Characteristic_CBUUID {
// STEP 11: 订阅关于感兴趣特征的持续通知; // “当你启用特征值的通知时,外围设备调用…… // peripheral(_:didUpdateValueFor:error:)” // // Notify Mandatory // peripheral.setNotifyValue(true, for: characteristic) } } // END for } // END func peripheral(... didDiscoverCharacteristicsFor service // STEP 12: 每当一个特征值定期更新或者发布一次时,我们都会收到通知; // 阅读并解译我们订阅的特征值 funcperipheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if characteristic.uuid == BLE_Heart_Rate_Measurement_Characteristic_CBUUID { // STEP 13: 通常我们需要将 BLE 的数据解析成人类可读的格式 let heartRate = deriveBeatsPerMinute(using: characteristic) DispatchQueue.main.async { () -> Voidin UIView.animate(withDuration: 1.0, animations: { self.beatsPerMinuteLabel.alpha = 1.0 self.beatsPerMinuteLabel.text = String(heartRate) }, completion: { (true) in self.beatsPerMinuteLabel.alpha = 0.0 }) } // END DispatchQueue.main.async...
} // END if characteristic.uuid ==... if characteristic.uuid == BLE_Body_Sensor_Location_Characteristic_CBUUID { // STEP 14: 通常我们需要将 BLE 的数据解析成人类可读的格式 let sensorLocation = readSensorLocation(using: characteristic)
DispatchQueue.main.async { () -> Voidin self.sensorLocationTextField.text = sensorLocation } } // END if characteristic.uuid ==... } // END func peripheral(... didUpdateValueFor characteristic // MARK: - Utilities funcderiveBeatsPerMinute(using heartRateMeasurementCharacteristic: CBCharacteristic) -> Int { let heartRateValue = heartRateMeasurementCharacteristic.value! // 转换为无符号 8 位整数数组 let buffer = [UInt8](heartRateValue)
// UInt8: “一个 8 位无符号整数类型。” // 在缓冲区的第一个字节(8 位)是标记(元数据,用于管理包中其余部分); // 如果最低有效位(LSB)是 0,心率(bpm)则是 UInt8 格式, // 如果 LSB 是 1,BPM 则是 UInt16 if ((buffer[0] & 0x01) == 0) { // 第二个字节:“心率的格式被设置为 UINT8” print("BPM is UInt8") // 将心率写入 HKHealthStore // healthKitInterface.writeHeartRateData(heartRate: Int(buffer[1])) returnInt(buffer[1]) } else { // 我从来没有看到过这个用例,所以我把它留给理论学家去争论 // 第二个和第三个字节:“心率的格式被设置为 UINT16” print("BPM is UInt16") return -1 } } // END func deriveBeatsPerMinute funcreadSensorLocation(using sensorLocationCharacteristic: CBCharacteristic) -> String { let sensorLocationValue = sensorLocationCharacteristic.value! // 转换为无符号 8 位整数数组 let buffer = [UInt8](sensorLocationValue) var sensorLocation = "" // 只看 8 位 if buffer[0] == 1 { sensorLocation = "Chest" } elseif buffer[0] == 2 { sensorLocation = "Wrist" } else { sensorLocation = "N/A" } return sensorLocation } // END func readSensorLocation funcdecodePeripheralState(peripheralState: CBPeripheralState) { switch peripheralState { case .disconnected: print("Peripheral state: disconnected") case .connected: print("Peripheral state: connected") case .connecting: print("Peripheral state: connecting") case .disconnecting: print("Peripheral state: disconnecting") } } // END func decodePeripheralState(peripheralState
} // END class HeartRateMonitorViewController
总结
我希望你喜欢这篇教程。买或者借一个 BLE 设备,然后使用我的代码或自己编写代码来连接它。遵循教程中所有我提供的超链接并且阅读它们。查阅 Bluetooth SIG 的 网页 以及苹果的 Core Bluetooth(这里 也可以看到)框架文档,你一定可以对蓝牙技术有一个概览。