for (NSString *userID in morningEventAttendees) { for (Person *person in employees) { if ([person.identifier isEqualToString:userID]) { [peopleAttending addObject:person]; } } }
// 现在 peopleAttendingMorningEvent 里面就有我们想要的东西了
我们也可以使用 predicate 来达到完全一样的效果:
NSPredicate *morningAttendees = [NSPredicate predicateWithFormat:@"SELF.identifier IN %@", peopleAttendingMorningEvent];
static var CounterKey = “_counter” ... objc_setAssociatedObject(trackableClass, &RuntimeConstants.CounterKey, counter+1, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
为什么我们使用 var 来修饰计数器的键这个静态属性并在传递到其他地方时使用引用?答案隐藏在 Swift 语言基础——字符串之中。字符串像其他所有的值类型一样,是按值传递的。那么,当你把它传入这个闭包时,这个字符串将会被复制到一个不同的地址,这会导致在关联对象表中产生一个完全不同的键。& 符号总是保证将相同的地址作为键参数的值。你可以尝试以下代码:
我盯着这段代码直到我的眼睛开始流血,感到这段代码可能存在问题。我首先想到的是分析它的时间复杂度。如果把原始数组的长度用 n 表示,再把最后想要得到的元素的数目用 m 表示,在分析代码之后可以得出,排序的复杂度是 O(nlogn),取前子集合的操作则更快,时间复杂度为 O(1)(取前子集合操作本身最慢时可能会达到 O(m),但对这里我们要处理的数组而言,由于它是可随机访问的集合,因此取前子集合操作能在常数时间中完成)。
这正是让我感到困惑的地方:获取一个序列的最小元素(使用 min() 函数)只需要单次遍历所有元素,或者说时间复杂度为 O(n)。将其所有元素进行完整排序需要的时间复杂度是 O(nlogn)。而从集合中获取 m 个数,当 m 比 n 小时,时间复杂度应该位于它们之间。且当 m 比 n 小非常多时,时间复杂度应该更接近 O(n)。
在我们的例子里,图片的数量会非常大(n 约为 55000),而我们想得到的元素数量却很小(m 为 20)。因此,这里应该存在有优化的空间。我们是否能够优化排序,使其仅排序出前 m 个元素?
答案是肯定的,我们能够在这个方向上进行一些优化。我将此函数命名为 smallest(_:by:),它接收 sort 和 prefix 函数的所有参数,也就是上面提到的 m 和用于排序做比较的闭包:
一处唾手可得的优化是:我们是在 55000 个元素的数组中查找 20 个最小的元素,其中我们检查的大部分(几乎是全部)元素不会落入到最后的 result 数组中。因此我们可以去检查元素是否比 result 数组中的最后一个元素要大,如果是,它就完全可以被跳过。因为当元素比 result 中的最后一个还要大时,再去查找插入的索引就没有任何意义了。
iflet last = result.last, areInIncreasingOrder(last, e) { continue }
在测试中,此处增加的判断可以减少线性搜索 result 数组 99.5% 的时间,算法整体上又会加快十倍左右。感谢 Greg Titus 告诉我此处可以优化──之前我完全没有想到这一点。
如果想更近一步的话,还可以做另一处(稍微难实现一些)的优化。此优化基于两处事实:第一,我们使用 index(where:) 来找出应在 result 数组中进行插入的位置;第二,result 数组总是保持有序的。index(where:) 通常情况下是一项线性操作,但如果是在一个已经排好序的数组中进行搜索,我们可以将线性搜索替换成二分搜索。我对此进行了尝试。
let scaleFactor = UIScreen.main.scale let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor) let size = imageView.bounds.size.applying(scale)
where:要求关联类型必须遵守特定协议,或者类型参数和关联类型必须保持一致。也可以用于在 case 中提供额外条件,用于满足控制表达式。
where 从句可以应用于多种场景。以下例子指明了 where 的主要应用场景,泛型中的模式匹配。
protocolNameable { var name:String {getset} }
funccreatedFormattedName(_ namedEntity:T) -> StringwhereT:Equatable { //只有当实体同时遵守 Nameable 和 Equatable 协议的时候,才允许调用这个函数 return"This things name is " + namedEntity.name }
和
for i in0…3where i % 2 == 0 { print(i) //打印 0 和 2 }
while:循环执行特定的一段语句,直到条件不满足时,停止循环。
while foo != bar { print("Keeps going until the foo == bar") }
表达式和类型中的关键字
Any:用于表示任意类型的实例,包括函数类型。
var anything = [Any]()
anything.append("Any Swift type can be added") anything.append(0) anything.append({(foo: String) -> Stringin"Passed in (foo)"})
as:类型转换运算符,用于尝试将值转成其它类型。
var anything = [Any]()
anything.append("Any Swift type can be added") anything.append(0) anything.append({(foo: String) -> Stringin"Passed in (foo)" })
let intInstance = anything[1] as? Int
或者
var anything = [Any]()
anything.append("Any Swift type can be added") anything.append(0) anything.append({(foo: String) -> Stringin"Passed in (foo)" })
for thing in anything { switch thing { case0asInt: print("It's zero and an Int type") caselet someInt asInt: print("It's an Int that's not zero but (someInt)") default: print("Who knows what it is") } }
do { try haveAWeekend(4) } catchWeekendError.Overtime(let hoursWorked) { print("You worked (hoursWorked) more than you should have") } catchWeekendError.WorkAllWeekend { print("You worked 48 hours :-0") } catch { print("Gulping the weekend exception") }
false:Swift 用于表示布尔值的两个常量值之一,true 的相反值。
let alwaysFalse = false let alwaysTrue = true
if alwaysFalse { print("Won't print, alwaysFalse is false 😉")}
//任何 Swift 类型或实例可以为 nil var statelessPerson:Person? = nil var statelessPlace:Place? = nil var statelessInt:Int? = nil var statelessString:String? = nil
你也许注意到了,我们目前还没有实现队列的协议。当我们进行编码时,通常希望事物之间能保持相对分离。并且希望能创建出一个概览从而方便我们去进行查找。有些类可能会逐渐变得非常大,解决这种情况的方法之一是使用扩展作用域。这样,每一个扩展倾向于只做一个任务(比如去遵循一个协议,处理存储与初始化,又或是嵌套类的声明等),事后再去查找时就会容易很多。让我们在这里也尝试使用这种方式。首先,实现一个 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(这里 也可以看到)框架文档,你一定可以对蓝牙技术有一个概览。