Objective-C中的Core Bluetooth蓝牙通信
Core Bluetooth框架概述
Core Bluetooth 框架是苹果公司为 iOS 和 macOS 开发者提供的用于与蓝牙低功耗(BLE)设备进行通信的框架。在 Objective-C 开发中,Core Bluetooth 使得与 BLE 设备交互变得相对容易,无论是从设备发现、连接,到服务与特征的读写操作,都有一套相对完善的 API 支持。
核心类介绍
- CBCentralManager:中心角色管理器,负责管理中心设备的状态,如扫描周边的 BLE 设备。例如,初始化一个中心管理器:
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
这里通过 initWithDelegate:queue:
方法初始化了一个 CBCentralManager
实例,并指定了代理 self
,队列设置为 nil
表示使用主线程队列。
- CBPeripheral:代表一个周边设备,一旦中心设备发现了周边设备,就会得到
CBPeripheral
实例。例如,在中心管理器代理方法中获取周边设备:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
// 这里的peripheral就是发现的周边设备实例
}
- CBService:每个
CBPeripheral
可以包含多个服务,这些服务定义了设备的功能。比如心率监测设备可能有心率服务。获取周边设备的服务:
[peripheral discoverServices:nil];
这里调用 discoverServices:
方法,参数 nil
表示发现所有服务。
- CBCharacteristic:每个服务可以包含多个特征,特征是数据的载体。例如心率服务中的心率值特征。发现服务中的特征:
[peripheral discoverCharacteristics:nil forService:service];
同样,参数 nil
表示发现服务中的所有特征。
中心模式开发流程
初始化中心管理器
首先,在项目中需要初始化 CBCentralManager
。这通常在视图控制器的 viewDidLoad
方法或者其他合适的初始化位置进行。
- (void)viewDidLoad {
[super viewDidLoad];
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
在初始化时,指定代理 self
,这意味着当前视图控制器需要遵循 CBCentralManagerDelegate
协议。该协议包含了一系列方法,用于处理中心管理器的状态变化以及设备发现等事件。
处理中心管理器状态变化
中心管理器有多种状态,如未授权、未初始化、准备好等。我们需要在代理方法中处理这些状态变化。
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@"Central manager state is unknown");
break;
case CBCentralManagerStateResetting:
NSLog(@"Central manager state is resetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@"BLE is unsupported on this device");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@"App is not authorized to use BLE");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@"BLE is powered off");
break;
case CBCentralManagerStatePoweredOn: {
NSLog(@"BLE is powered on, start scanning");
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
break;
default:
break;
}
}
在 CBCentralManagerStatePoweredOn
状态下,我们调用 scanForPeripheralsWithServices:options:
方法开始扫描周边设备。参数 nil
表示扫描所有服务,options
参数可以用于设置扫描的一些选项,如是否允许重复扫描等。
扫描并发现周边设备
当中心管理器开始扫描后,一旦发现周边设备,就会调用代理方法 centralManager:didDiscoverPeripheral:advertisementData:RSSI:
。
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
NSLog(@"Discovered peripheral: %@, RSSI: %@", peripheral.name, RSSI);
// 可以在这里对发现的设备进行筛选,比如根据设备名称或UUID
if ([peripheral.name containsString:@"TargetDevice"]) {
[self.centralManager stopScan];
self.discoveredPeripheral = peripheral;
self.discoveredPeripheral.delegate = self;
[self.centralManager connectPeripheral:self.discoveredPeripheral options:nil];
}
}
在这个方法中,我们首先打印出发现的设备名称和信号强度(RSSI)。然后,通过判断设备名称是否包含特定字符串来筛选目标设备。如果找到了目标设备,就停止扫描,保存发现的周边设备实例,并设置其代理为当前视图控制器(当前视图控制器需要遵循 CBPeripheralDelegate
协议),最后连接到该设备。
连接周边设备
连接周边设备通过 connectPeripheral:options:
方法进行。连接成功或失败都会触发相应的代理方法。
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"Connected to peripheral: %@", peripheral.name);
[peripheral discoverServices:nil];
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"Failed to connect to peripheral: %@, error: %@", peripheral.name, error);
}
在 didConnectPeripheral:
方法中,连接成功后我们开始发现设备的服务。而在 didFailToConnectPeripheral:error:
方法中,我们打印出连接失败的设备名称和错误信息。
发现服务与特征
一旦连接成功并开始发现服务,当服务发现完成后,会调用 peripheral:didDiscoverServices:
代理方法。
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
if (error) {
NSLog(@"Error discovering services: %@", error);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service: %@", service.UUID);
[peripheral discoverCharacteristics:nil forService:service];
}
}
在这个方法中,我们首先检查是否有错误发生。如果没有错误,就遍历设备的所有服务,并打印出服务的 UUID。然后,针对每个服务,调用 discoverCharacteristics:forService:
方法来发现服务中的特征。当特征发现完成后,会调用 peripheral:didDiscoverCharacteristicsForService:error:
代理方法。
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if (error) {
NSLog(@"Error discovering characteristics: %@", error);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"Discovered characteristic: %@", characteristic.UUID);
// 这里可以根据特征UUID进一步判断是否是我们需要的特征
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"YOUR_CHARACTERISTIC_UUID"]]) {
// 对特定特征进行操作,比如读取或设置通知
}
}
}
同样,先检查错误,然后遍历服务中的所有特征并打印其 UUID。如果找到了特定 UUID 的特征,就可以对其进行相应的操作,如读取数据或设置通知。
读取与写入特征值
- 读取特征值:要读取特征值,首先要确保特征支持读取操作(可以通过
characteristic.properties
判断)。然后调用readValueForCharacteristic:
方法。
if (characteristic.properties & CBCharacteristicPropertyRead) {
[peripheral readValueForCharacteristic:characteristic];
}
当读取操作完成后,会调用 peripheral:didUpdateValueForCharacteristic:error:
代理方法。
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"Error reading characteristic value: %@", error);
return;
}
NSData *data = characteristic.value;
// 根据数据格式进行解析,比如如果是NSString格式
NSString *stringValue = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Read characteristic value: %@", stringValue);
}
- 写入特征值:写入特征值同样要先判断特征是否支持写入操作(通过
characteristic.properties
判断)。然后调用writeValue:forCharacteristic:type:
方法。
if (characteristic.properties & CBCharacteristicPropertyWrite) {
NSData *dataToWrite = [@"Hello, BLE device" dataUsingEncoding:NSUTF8StringEncoding];
[peripheral writeValue:dataToWrite forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
这里的 type
参数有两种选择,CBCharacteristicWriteWithResponse
表示写入后等待设备响应,CBCharacteristicWriteWithoutResponse
表示不等待响应直接写入。写入操作完成后,如果是 CBCharacteristicWriteWithResponse
类型,会调用 peripheral:didWriteValueForCharacteristic:error:
代理方法。
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"Error writing characteristic value: %@", error);
} else {
NSLog(@"Characteristic value written successfully");
}
}
设置特征通知
为了实时获取设备数据,通常需要设置特征通知。首先要确保特征支持通知操作(通过 characteristic.properties
判断)。然后调用 setNotifyValue:forCharacteristic:
方法。
if (characteristic.properties & CBCharacteristicPropertyNotify) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
当设备有新的数据更新时,会调用 peripheral:didUpdateValueForCharacteristic:error:
代理方法,和读取特征值后的回调方法相同,在这里可以处理新的数据。
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"Error receiving characteristic update: %@", error);
return;
}
NSData *data = characteristic.value;
// 解析数据并处理
// 例如,如果是温度数据,假设数据格式为float
float temperature;
[data getBytes:&temperature length:sizeof(float)];
NSLog(@"Received temperature: %f", temperature);
}
周边模式开发流程
初始化周边管理器
在周边模式下,需要初始化 CBPeripheralManager
。同样,在合适的初始化位置进行。
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
这里也指定了代理 self
,当前视图控制器需要遵循 CBPeripheralManagerDelegate
协议。
处理周边管理器状态变化
周边管理器也有多种状态,通过代理方法 peripheralManagerDidUpdateState:
处理。
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
switch (peripheral.state) {
case CBPeripheralManagerStateUnknown:
NSLog(@"Peripheral manager state is unknown");
break;
case CBPeripheralManagerStateResetting:
NSLog(@"Peripheral manager state is resetting");
break;
case CBPeripheralManagerStateUnsupported:
NSLog(@"BLE is unsupported on this device for peripheral mode");
break;
case CBPeripheralManagerStateUnauthorized:
NSLog(@"App is not authorized to use BLE in peripheral mode");
break;
case CBPeripheralManagerStatePoweredOff:
NSLog(@"BLE is powered off for peripheral mode");
break;
case CBPeripheralManagerStatePoweredOn: {
NSLog(@"BLE is powered on for peripheral mode, start advertising");
[self startAdvertising];
}
break;
default:
break;
}
}
在 CBPeripheralManagerStatePoweredOn
状态下,调用 startAdvertising
方法开始广播。
创建服务与特征
在广播之前,需要创建服务和特征。首先创建一个服务:
CBService *myService = [[CBService alloc] initWithType:[CBUUID UUIDWithString:@"YOUR_SERVICE_UUID"] primary:YES];
然后创建特征:
CBCharacteristic *myCharacteristic = [[CBCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"YOUR_CHARACTERISTIC_UUID"] properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
这里设置了特征的属性为可读和可通知,权限为可读。将特征添加到服务中:
myService.characteristics = @[myCharacteristic];
最后将服务添加到周边管理器中:
[self.peripheralManager addService:myService];
开始广播
广播是让周边设备能够被中心设备发现的关键步骤。
- (void)startAdvertising {
NSDictionary *advertisementData = @{
CBAdvertisementDataServiceUUIDsKey: @[[CBUUID UUIDWithString:@"YOUR_SERVICE_UUID"]],
CBAdvertisementDataLocalNameKey: @"My BLE Peripheral"
};
[self.peripheralManager startAdvertising:advertisementData];
}
这里通过 startAdvertising:
方法开始广播,广播数据中包含了服务 UUID 和本地设备名称。
处理中心设备连接
当有中心设备连接到周边设备时,会调用代理方法 peripheralManager:didConnectPeripheral:
。
- (void)peripheralManager:(CBPeripheralManager *)peripheral didConnectPeripheral:(CBPeripheral *)central {
NSLog(@"Connected to central: %@", central.name);
}
同样,当连接断开时,会调用 peripheralManager:didDisconnectPeripheral:error:
代理方法。
- (void)peripheralManager:(CBPeripheralManager *)peripheral didDisconnectPeripheral:(CBPeripheral *)central error:(NSError *)error {
NSLog(@"Disconnected from central: %@, error: %@", central.name, error);
}
处理中心设备对特征的读取与写入
- 处理读取请求:当中心设备请求读取特征值时,会调用
peripheralManager:didReceiveReadRequest:forCharacteristic:
代理方法。
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request forCharacteristic:(CBCharacteristic *)characteristic {
if (request.offset > characteristic.value.length) {
request.error = [NSError errorWithDomain:CBATTErrorDomain code:CBATTErrorInvalidOffset userInfo:nil];
[self.peripheralManager respondToRequest:request withResult:CBATTErrorInvalidOffset];
return;
}
NSData *subdata = [characteristic.value subdataWithRange:NSMakeRange(request.offset, characteristic.value.length - request.offset)];
request.value = subdata;
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}
这里首先检查请求的偏移量是否有效,如果无效则返回错误。如果有效,则根据偏移量获取相应的数据,并返回成功响应。
- 处理写入请求:当中心设备请求写入特征值时,会调用
peripheralManager:didReceiveWriteRequests:
代理方法。
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
for (CBATTRequest *request in requests) {
if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
// 更新特征值
request.characteristic.value = request.value;
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
} else {
[self.peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
}
这里遍历所有的写入请求,首先检查特征是否支持写入操作。如果支持,则更新特征值并返回成功响应;如果不支持,则返回写入不允许的错误响应。
向中心设备发送通知
周边设备可以主动向已连接的中心设备发送通知。首先要确保中心设备已经订阅了通知。然后通过 updateValue:forCharacteristic:onSubscribedCentrals:
方法发送通知。
NSData *newData = [@"New data for notification" dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheralManager updateValue:newData forCharacteristic:myCharacteristic onSubscribedCentrals:nil];
这里假设 myCharacteristic
是需要发送通知的特征,newData
是要发送的数据。
常见问题与解决方法
连接失败问题
- 原因分析:可能是设备未开启蓝牙、设备不支持 BLE、应用未获得授权等。
- 解决方法:在
centralManagerDidUpdateState:
方法中,根据不同的状态进行提示或处理。例如,如果是CBCentralManagerStateUnauthorized
状态,可以引导用户去设置中开启应用的蓝牙权限。
数据解析问题
- 原因分析:BLE 设备返回的数据格式可能多种多样,如二进制、十六进制等,解析不当就会出错。
- 解决方法:在处理数据前,要清楚设备返回的数据格式。例如,如果是二进制数据表示温度,需要根据数据长度和编码方式将其转换为实际的温度值。可以使用
NSData
的相关方法,如getBytes:length:
来获取字节数据并进行解析。
广播问题
- 原因分析:广播数据设置不正确、周边管理器状态异常等都可能导致广播失败。
- 解决方法:在
peripheralManagerDidUpdateState:
方法中确保周边管理器状态为CBPeripheralManagerStatePoweredOn
时才开始广播。检查广播数据的格式和内容,确保包含了正确的服务 UUID 和其他必要信息。
性能优化
减少扫描时间
- 策略:在发现目标设备后,及时停止扫描。例如,在
centralManager:didDiscoverPeripheral:advertisementData:RSSI:
方法中,一旦发现目标设备,就调用[self.centralManager stopScan]
方法。 - 原理:持续扫描会消耗设备的电量和系统资源,及时停止扫描可以减少这些消耗。
合理设置通知频率
- 策略:对于一些变化频繁的数据特征,根据实际需求合理设置通知频率。如果数据变化不频繁,可以适当降低通知频率。
- 原理:过高的通知频率会增加设备之间的数据传输量,消耗电量和带宽。合理设置通知频率可以在保证数据及时性的同时,降低资源消耗。
优化数据处理
- 策略:在处理大量 BLE 数据时,采用高效的数据解析和存储方法。例如,对于连续的传感器数据,可以采用队列或环形缓冲区来存储,避免频繁的内存分配和释放。
- 原理:高效的数据处理方法可以提高程序的运行效率,减少卡顿现象,提升用户体验。
通过以上对 Objective-C 中 Core Bluetooth 蓝牙通信的详细介绍,从框架概述、中心与周边模式开发流程,到常见问题解决和性能优化,开发者可以全面掌握 Core Bluetooth 在 Objective-C 项目中的应用,开发出稳定、高效的 BLE 通信应用程序。无论是开发智能家居控制、健康监测等各类 BLE 相关应用,都能以此为基础进行深入开发。