Objective-C蓝牙开发与Core Bluetooth实战
一、Core Bluetooth框架简介
Core Bluetooth 框架是 iOS 和 macOS 上用于蓝牙低功耗(Bluetooth Low Energy,BLE)开发的核心框架。它提供了一组丰富的 API,让开发者能够轻松地与 BLE 设备进行交互,无论是作为中心设备(central)发起连接,还是作为外设设备(peripheral)广播数据。
在 iOS 开发中,Core Bluetooth 框架被广泛应用于健康监测、智能家居、物联网等领域。例如,与智能手环连接获取运动数据,或者控制智能家居设备等场景。
在 macOS 开发中,同样可以利用 Core Bluetooth 框架实现与 BLE 设备的交互,为 Mac 应用增添更多功能。
二、Objective - C 中的蓝牙开发环境设置
- 导入框架 在你的 Xcode 项目中,首先需要导入 Core Bluetooth 框架。在项目导航栏中,选中项目,然后在“General”选项卡下的“Linked Frameworks and Libraries”部分,点击“+”按钮,搜索并添加“Core Bluetooth.framework”。
#import <CoreBluetooth/CoreBluetooth.h>
- 权限配置
在 iOS 中,使用蓝牙功能需要获取用户的权限。对于 iOS 13 及以上版本,需要在
Info.plist
文件中添加以下权限描述:
NSBluetoothAlwaysUsageDescription
:描述应用为何需要始终使用蓝牙权限。NSBluetoothPeripheralUsageDescription
:描述应用为何需要使用蓝牙与外设通信。
例如:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Your bluetooth is used to connect with accessories.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Your bluetooth is used to connect with accessories.</string>
三、作为中心设备(Central)开发
- 中心设备管理器
在 Objective - C 中,
CBCentralManager
类用于管理中心设备的状态和操作。首先,创建一个CBCentralManager
实例,并实现相关的代理方法。
@interface ViewController () <CBCentralManagerDelegate>
@property (nonatomic, strong) CBCentralManager *centralManager;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
switch (central.state) {
case CBCentralManagerStatePoweredOff:
NSLog(@"蓝牙已关闭");
break;
case CBCentralManagerStatePoweredOn:
NSLog(@"蓝牙已开启,开始扫描");
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
break;
case CBCentralManagerStateUnauthorized:
NSLog(@"没有蓝牙权限");
break;
case CBCentralManagerStateUnknown:
NSLog(@"蓝牙状态未知");
break;
case CBCentralManagerStateUnsupported:
NSLog(@"设备不支持蓝牙");
break;
case CBCentralManagerStateResetting:
NSLog(@"蓝牙正在重置");
break;
}
}
- 扫描外设
在
centralManagerDidUpdateState:
方法中,当蓝牙状态为CBCentralManagerStatePoweredOn
时,调用scanForPeripheralsWithServices:options:
方法开始扫描外设。
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
NSLog(@"发现外设: %@,RSSI: %@", peripheral.name, RSSI);
[self.centralManager connectPeripheral:peripheral options:nil];
}
在这个方法中,当发现新的外设时,会打印出外设的名称和信号强度(RSSI),并且尝试连接该外设。
- 连接外设
连接外设后,需要实现
centralManager:didConnectPeripheral:
方法来处理连接成功后的操作,例如发现服务和特征。
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"连接外设成功: %@", peripheral.name);
peripheral.delegate = self;
[peripheral discoverServices:nil];
}
这里设置了外设的代理,并开始发现外设的服务。
- 发现服务和特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
if (error) {
NSLog(@"发现服务失败: %@", error);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"发现服务: %@", service.UUID);
[peripheral discoverCharacteristics:nil forService:service];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if (error) {
NSLog(@"发现特征失败: %@", error);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"发现特征: %@", characteristic.UUID);
if (characteristic.properties & CBCharacteristicPropertyRead) {
[peripheral readValueForCharacteristic:characteristic];
}
if (characteristic.properties & CBCharacteristicPropertyNotify) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
在 peripheral:didDiscoverServices:
方法中,遍历发现的服务,并对每个服务发现其特征。在 peripheral:didDiscoverCharacteristicsForService:error:
方法中,根据特征的属性决定是否读取数据或者设置通知。
- 读取和写入数据
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"读取特征值失败: %@", error);
return;
}
NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(@"读取到的数据: %@", value);
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"设置通知失败: %@", error);
return;
}
if (characteristic.isNotifying) {
NSLog(@"已开启通知");
} else {
NSLog(@"已关闭通知");
}
}
- (void)writeDataToCharacteristic:(CBCharacteristic *)characteristic data:(NSData *)data {
if (characteristic.properties & CBCharacteristicPropertyWrite) {
[self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
peripheral:didUpdateValueForCharacteristic:error:
方法用于处理读取到的数据。peripheral:didUpdateNotificationStateForCharacteristic:error:
方法用于处理通知状态的更新。writeDataToCharacteristic:data:
方法用于向特征写入数据。
四、作为外设设备(Peripheral)开发
- 外设管理器
在 Objective - C 中,
CBPeripheralManager
类用于管理外设设备的状态和操作。创建一个CBPeripheralManager
实例,并实现相关的代理方法。
@interface ViewController () <CBPeripheralManagerDelegate>
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBMutableService *myService;
@property (nonatomic, strong) CBMutableCharacteristic *myCharacteristic;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
switch (peripheral.state) {
case CBPeripheralManagerStatePoweredOff:
NSLog(@"蓝牙已关闭");
break;
case CBPeripheralManagerStatePoweredOn:
NSLog(@"蓝牙已开启,开始设置服务和特征");
[self setupServiceAndCharacteristic];
break;
case CBPeripheralManagerStateUnauthorized:
NSLog(@"没有蓝牙权限");
break;
case CBPeripheralManagerStateUnknown:
NSLog(@"蓝牙状态未知");
break;
case CBPeripheralManagerStateUnsupported:
NSLog(@"设备不支持蓝牙");
break;
case CBPeripheralManagerStateResetting:
NSLog(@"蓝牙正在重置");
break;
}
}
- 设置服务和特征
- (void)setupServiceAndCharacteristic {
CBUUID *serviceUUID = [CBUUID UUIDWithString:@"12345678 - 1234 - 5678 - 1234 - 567890ABCDEF"];
self.myService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
CBUUID *characteristicUUID = [CBUUID UUIDWithString:@"12345678 - 1234 - 5678 - 1234 - 567890ABCDE0"];
self.myCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
self.myService.characteristics = @[self.myCharacteristic];
[self.peripheralManager addService:self.myService];
}
这里创建了一个自定义的服务和特征,并添加到外设管理器中。
- 广播数据
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {
if (error) {
NSLog(@"添加服务失败: %@", error);
return;
}
NSLog(@"服务已添加,开始广播");
[self.peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey : @[service.UUID]}];
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {
if (error) {
NSLog(@"广播失败: %@", error);
return;
}
NSLog(@"正在广播");
}
在 peripheralManager:didAddService:error:
方法中,添加服务成功后开始广播。peripheralManagerDidStartAdvertising:error:
方法用于处理广播开始的结果。
- 处理中心设备的请求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
if (request.characteristic == self.myCharacteristic) {
NSData *data = [@"Hello, Bluetooth!" dataUsingEncoding:NSUTF8StringEncoding];
request.value = data;
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
for (CBATTRequest *request in requests) {
if (request.characteristic == self.myCharacteristic) {
NSString *value = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
NSLog(@"接收到写入的数据: %@", value);
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}
}
}
peripheralManager:didReceiveReadRequest:
方法用于处理中心设备的读取请求,peripheralManager:didReceiveWriteRequests:
方法用于处理中心设备的写入请求。
五、常见问题及解决方案
- 连接失败
连接失败可能有多种原因,例如蓝牙设备距离过远、设备不支持 BLE 协议、设备处于繁忙状态等。可以通过实现
centralManager:didFailToConnectPeripheral:error:
方法来获取连接失败的详细错误信息。
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"连接失败: %@", error);
}
根据错误信息,可以采取相应的措施,如提示用户调整设备位置、检查设备兼容性等。
- 数据丢失或乱序 在蓝牙数据传输过程中,可能会出现数据丢失或乱序的情况。这通常是由于蓝牙信号不稳定或者数据传输速度过快导致的。可以通过以下方法来解决:
- 分包传输:将大数据分成多个小的数据包进行传输,减少单个数据包丢失的影响。
- 增加校验和:在每个数据包中添加校验和,接收方可以通过校验和来验证数据的完整性。
- 使用可靠的传输模式:例如,在写入数据时使用
CBCharacteristicWriteWithResponse
类型,确保数据被正确接收。
- 权限问题 如果在运行应用时没有获取到蓝牙权限,需要引导用户在设备设置中开启蓝牙权限。可以使用以下代码跳转到设备的设置页面:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
六、优化蓝牙开发性能
- 合理设置扫描参数
在扫描外设时,可以通过设置
scanForPeripheralsWithServices:options:
方法的options
参数来优化扫描性能。例如,可以设置CBCentralManagerScanOptionAllowDuplicatesKey
为NO
,避免重复发现相同的外设。
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @NO}];
-
控制连接数量 同时连接过多的蓝牙设备可能会导致性能下降,甚至连接失败。在应用中,需要根据实际需求合理控制连接的设备数量。
-
优化数据传输
- 减少不必要的数据传输:只传输必要的数据,避免传输大量冗余信息。
- 选择合适的传输频率:根据应用场景,选择合适的数据传输频率,避免过于频繁地传输数据,增加功耗。
七、实际应用案例分析
- 智能健康手环连接 假设我们要开发一个与智能健康手环连接的应用,获取手环的运动数据,如步数、心率等。
- 作为中心设备:按照前面介绍的中心设备开发流程,扫描手环设备,连接后发现手环提供的运动数据服务和相关特征。例如,心率数据可能在一个具有
CBCharacteristicPropertyNotify
属性的特征中,我们可以设置通知来实时获取心率数据。
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if (error) {
NSLog(@"发现特征失败: %@", error);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"心率特征 UUID"]]) {
if (characteristic.properties & CBCharacteristicPropertyNotify) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"读取心率数据失败: %@", error);
return;
}
NSNumber *heartRate = [NSNumber numberWithData:characteristic.value];
NSLog(@"当前心率: %@", heartRate);
}
- 智能家居控制 在智能家居场景中,手机作为中心设备连接智能家居设备(如智能灯)。智能灯作为外设设备,提供控制灯光开关和亮度调节的服务和特征。
- 作为中心设备:发现智能灯设备并连接后,找到控制灯光开关的特征,通过写入数据来控制灯光。
- (void)turnOnLight:(CBCharacteristic *)characteristic {
NSData *data = [@(1) dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
- (void)turnOffLight:(CBCharacteristic *)characteristic {
NSData *data = [@(0) dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
- 作为外设设备:智能家居设备需要设置相应的服务和特征,处理中心设备的写入请求,根据接收到的数据控制灯光。
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
for (CBATTRequest *request in requests) {
if (request.characteristic == self.lightSwitchCharacteristic) {
NSString *value = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
if ([value isEqualToString:@"1"]) {
// 打开灯光
} else if ([value isEqualToString:@"0"]) {
// 关闭灯光
}
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}
}
}
通过以上详细的介绍和代码示例,相信你已经对 Objective - C 中利用 Core Bluetooth 框架进行蓝牙开发有了深入的了解,可以在实际项目中灵活应用这些知识来实现各种蓝牙相关的功能。无论是开发移动应用还是桌面应用,Core Bluetooth 框架都为与蓝牙低功耗设备的交互提供了强大的支持。在实际开发过程中,还需要不断优化代码,以提高应用的性能和稳定性,满足不同用户的需求。同时,随着蓝牙技术的不断发展,新的功能和特性也会不断涌现,开发者需要持续关注并学习,以保持技术的先进性。