static var CounterKey = “_counter” ... objc_setAssociatedObject(trackableClass, &RuntimeConstants.CounterKey, counter+1, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
为什么我们使用 var 来修饰计数器的键这个静态属性并在传递到其他地方时使用引用?答案隐藏在 Swift 语言基础——字符串之中。字符串像其他所有的值类型一样,是按值传递的。那么,当你把它传入这个闭包时,这个字符串将会被复制到一个不同的地址,这会导致在关联对象表中产生一个完全不同的键。& 符号总是保证将相同的地址作为键参数的值。你可以尝试以下代码:
对于开发者来说,复杂性是最大的敌人,因此我会去了解那些可以帮助我管理混乱的新技术。Swift 中的“面向协议编程”(POP)是最近(至少自2015年以来)引起广泛关注的“热门”方法之一。在这里我们将使用 Swift 4。在我自己编写代码时,发现 POP 很有前途。更吸引人的是,Apple 宣称 “Swift 的核心是面对协议的”。我想在一个正式的报告中分享关于 POP 的经验,一篇关于这个新兴技术清晰而简洁的教程。
我将解释关键概念,提供大量代码示例,无法避免的将 POP 和 OOP (面向对象编程)进行比较,并对面向流行编程(FOP?)的人群所声称的 POP 是解决所有问题的灵丹妙药这一说法进行泼冷水。
学习和采用像 POP 这样的新技术并不需要绝对的唯一。POP 和 OOP 不仅可以共存,还可以互相协助。对于大多数开发者包括我自己,掌握 POP 需要时间和耐心。因为 POP 真的很重要,所以我将教程分成两篇文章。本文将主要介绍和解释 Swift 的协议和 POP。第二篇文章将深入研究 POP 的高级应用方式(比如从协议开始构建应用程序的功能),范型协议,从引用类型到值类型转变背后的动机,列举 POP 的利弊,列举 OOP 的利弊,比较 OOP 和 POP,阐述为什么“Swift 是面向协议的”,并且深入研究一个被称为 “局部推理” 的概念,它被认为是通过使用 POP 增强的。这次我们只会粗略涉及一些高级主题。
引言
作为软件开发者,管理复杂性本质上是我们最应该关注的问题。当我们尝试学习 POP 这项新技术时,你可能无法从时间的投资中看到即时回报。但是,就像你对我的认识有个过程一样,你将会了解 POP 处理复杂性的方法,同时为你提供另一种工具来控制软件系统中固有的混乱。
我听到越来越多关于 POP 的讨论,但是却很少看到使用这种方式编写的产品代码,换句话说,我还没有看到有很多人从协议而不是类开始创建应用程序的功能。这不仅仅是因为人类有抗拒改变的倾向。学习一种全新的范式并将其付诸实践,说起来容易做起来难。在我编写新应用程序时,逐渐发现自己开始使用 POP 来设计和实现功能 — 有组织的且自然而然的。
伴随着新潮流带来的刺激,很多人都在谈论用 POP 取代 OOP。我认为除非像 Swift 这样的 POP 语言被广泛改进,否则这是不可能发生的 — 也或许根本就不会发生。我是个实用主义者,而不是追求时髦的人。在开发新的 Swift 项目时,我发现自己的行为是一种折衷的方法。我在合理的地方利用 OOP,而用 POP 更合适的地方也不会死脑筋的一定要使用 OOP,这样反而了解到这两种模式并不相互排斥。我把这两种技术结合在一起。在本期由两部分组成的 POP 教程中,你将了解我在说什么。
我投入到 OOP 中已经有很久了。1990 年,我买了一个零售版本的 Turbo Pascal。在使用了 OOP 大约一年后,我开始设计、开发和发布面向对象的应用程序产品。我成了一个忠粉。当我发现可以扩展和增强自己的类,简直兴奋的飞起。随着时间的推移,Microsoft 和 Apple 等公司开始开发基于 OOP 的大型代码库,如 Microsoft Foundation Classes(MFC)和 .NET,以及 iOS 和 OS X SDK。现在,开发人员在开发新应用程序时很少需要重新造轮子。没有完美的方法,OOP 也有一些缺点,但是优点仍然大于缺点。我们将花一些时间来比较 OOP 和 POP。
所有定义协议属性和方法的规则都在 Apple 的 Swift 文档 中进行了总结。比如,在协议中定义属性永远不要用 let 关键字。只读属性规定使用 var 关键字,并在后面单独跟上 { get }。如果有一个方法改变了一个或多个属性,你需要标记它为 mutating。你需要知道为什么我重写的 == 和 != 操作符被定义为 static。如果你不知道,找出原因将会是一个很好的练习。
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(这里 也可以看到)框架文档,你一定可以对蓝牙技术有一个概览。