MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C蓝牙开发与Core Bluetooth实战

2023-04-053.6k 阅读

一、Core Bluetooth框架简介

Core Bluetooth 框架是 iOS 和 macOS 上用于蓝牙低功耗(Bluetooth Low Energy,BLE)开发的核心框架。它提供了一组丰富的 API,让开发者能够轻松地与 BLE 设备进行交互,无论是作为中心设备(central)发起连接,还是作为外设设备(peripheral)广播数据。

在 iOS 开发中,Core Bluetooth 框架被广泛应用于健康监测、智能家居、物联网等领域。例如,与智能手环连接获取运动数据,或者控制智能家居设备等场景。

在 macOS 开发中,同样可以利用 Core Bluetooth 框架实现与 BLE 设备的交互,为 Mac 应用增添更多功能。

二、Objective - C 中的蓝牙开发环境设置

  1. 导入框架 在你的 Xcode 项目中,首先需要导入 Core Bluetooth 框架。在项目导航栏中,选中项目,然后在“General”选项卡下的“Linked Frameworks and Libraries”部分,点击“+”按钮,搜索并添加“Core Bluetooth.framework”。
#import <CoreBluetooth/CoreBluetooth.h>
  1. 权限配置 在 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)开发

  1. 中心设备管理器 在 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;
    }
}
  1. 扫描外设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),并且尝试连接该外设。

  1. 连接外设 连接外设后,需要实现 centralManager:didConnectPeripheral: 方法来处理连接成功后的操作,例如发现服务和特征。
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"连接外设成功: %@", peripheral.name);
    peripheral.delegate = self;
    [peripheral discoverServices:nil];
}

这里设置了外设的代理,并开始发现外设的服务。

  1. 发现服务和特征
- (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: 方法中,根据特征的属性决定是否读取数据或者设置通知。

  1. 读取和写入数据
- (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)开发

  1. 外设管理器 在 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;
    }
}
  1. 设置服务和特征
- (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];
}

这里创建了一个自定义的服务和特征,并添加到外设管理器中。

  1. 广播数据
- (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: 方法用于处理广播开始的结果。

  1. 处理中心设备的请求
- (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: 方法用于处理中心设备的写入请求。

五、常见问题及解决方案

  1. 连接失败 连接失败可能有多种原因,例如蓝牙设备距离过远、设备不支持 BLE 协议、设备处于繁忙状态等。可以通过实现 centralManager:didFailToConnectPeripheral:error: 方法来获取连接失败的详细错误信息。
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"连接失败: %@", error);
}

根据错误信息,可以采取相应的措施,如提示用户调整设备位置、检查设备兼容性等。

  1. 数据丢失或乱序 在蓝牙数据传输过程中,可能会出现数据丢失或乱序的情况。这通常是由于蓝牙信号不稳定或者数据传输速度过快导致的。可以通过以下方法来解决:
  • 分包传输:将大数据分成多个小的数据包进行传输,减少单个数据包丢失的影响。
  • 增加校验和:在每个数据包中添加校验和,接收方可以通过校验和来验证数据的完整性。
  • 使用可靠的传输模式:例如,在写入数据时使用 CBCharacteristicWriteWithResponse 类型,确保数据被正确接收。
  1. 权限问题 如果在运行应用时没有获取到蓝牙权限,需要引导用户在设备设置中开启蓝牙权限。可以使用以下代码跳转到设备的设置页面:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];

六、优化蓝牙开发性能

  1. 合理设置扫描参数 在扫描外设时,可以通过设置 scanForPeripheralsWithServices:options: 方法的 options 参数来优化扫描性能。例如,可以设置 CBCentralManagerScanOptionAllowDuplicatesKeyNO,避免重复发现相同的外设。
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @NO}];
  1. 控制连接数量 同时连接过多的蓝牙设备可能会导致性能下降,甚至连接失败。在应用中,需要根据实际需求合理控制连接的设备数量。

  2. 优化数据传输

  • 减少不必要的数据传输:只传输必要的数据,避免传输大量冗余信息。
  • 选择合适的传输频率:根据应用场景,选择合适的数据传输频率,避免过于频繁地传输数据,增加功耗。

七、实际应用案例分析

  1. 智能健康手环连接 假设我们要开发一个与智能健康手环连接的应用,获取手环的运动数据,如步数、心率等。
  • 作为中心设备:按照前面介绍的中心设备开发流程,扫描手环设备,连接后发现手环提供的运动数据服务和相关特征。例如,心率数据可能在一个具有 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);
}
  1. 智能家居控制 在智能家居场景中,手机作为中心设备连接智能家居设备(如智能灯)。智能灯作为外设设备,提供控制灯光开关和亮度调节的服务和特征。
  • 作为中心设备:发现智能灯设备并连接后,找到控制灯光开关的特征,通过写入数据来控制灯光。
- (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 框架都为与蓝牙低功耗设备的交互提供了强大的支持。在实际开发过程中,还需要不断优化代码,以提高应用的性能和稳定性,满足不同用户的需求。同时,随着蓝牙技术的不断发展,新的功能和特性也会不断涌现,开发者需要持续关注并学习,以保持技术的先进性。