CoreBluetooth

本文详细介绍如何使用iOS CoreBluetooth框架进行蓝牙低功耗(BLE)设备的发现、连接、交互等操作,包括CBCentralManager和CBPeripheral的使用方法。

硬體週邊Tips

Contents

 [hide

藍牙

BLE(CoreBluetooth)

Bluetooth 4.0之後就將通訊模式分為高速及低速種類,目前將低速低耗能簡稱為BLE,可以連接一些量測型的感測器類型像:心跳計、血壓…等,這使得iDevice不再是利用MFi認證過的Dock才能與iDevice連接,增加APP型態的多元。

如果想要跟BLE週邊連接,iOS提供了CoreBluetooth framework來與週邊連接,整個程式中分為Discover、Connect、Explore、Interact,下面將會以從連線至BLE讀取到資料為原則來介紹。

Discover/Connect

BLE FLOW1.png

依照箭頭方向由上而下為順序來依序完成Discover與Connect流程,下面會針對此流程加上範例來介紹

CBCentralManager

要使用CoreBluetooth就要先了解一下CBCentralManager,這個Object掌控整個BLE的管理,一開始要先對CBCentralManager來做個初始化

CBCentralManager *CM = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
centralManagerDidUpdateState

在初始化時指定Delegate為self,並在.h內加上Delegate宣告:

@interface TestCoreBluetooth : NSObject<CBCentralManagerDelegate> {
:
:
:
}

加完宣告後就需要完成centralManagerDidUpdateState這個Delegate,

-(void)centralManagerDidUpdateState:(CBCentralManager*)cManager
{
    NSMutableString* nsmstring=[NSMutableString stringWithString:@"UpdateState:"];
    BOOL isWork=FALSE;
    switch (cManager.state) {
        case CBCentralManagerStateUnknown:
            [nsmstring appendString:@"Unknown\n"];
            break;
        case CBCentralManagerStateUnsupported:
            [nsmstring appendString:@"Unsupported\n"];            
            break;
        case CBCentralManagerStateUnauthorized:
            [nsmstring appendString:@"Unauthorized\n"];
            break;
        case CBCentralManagerStateResetting:
            [nsmstring appendString:@"Resetting\n"];
            break;
        case CBCentralManagerStatePoweredOff:
            [nsmstring appendString:@"PoweredOff\n"];
            if (connectedPeripheral!=NULL){
                [CM cancelPeripheralConnection:connectedPeripheral];
            }
            break;
        case CBCentralManagerStatePoweredOn:
            [nsmstring appendString:@"PoweredOn\n"];
            isWork=TRUE;
            break;
        default:
            [nsmstring appendString:@"none\n"]; 
            break;
    }
    NSLog(@"%@",nsmstring);
    [delegate didUpdateState:isWork message:nsmstring getStatus:cManager.state];
}

依照centralManagerDidUpdateState來結果來判斷iDevice是否支援BLE,畢竟BLE是在iphone 4s、New iPad之後才有的,可以根據此項來決定APP的功能或其他提示使用者的動作。

scanForPeripheralsWithServices

確定周邊支援BLE且運作正常後,我們就要來開啟BLE搜尋功能來尋找BLE的週邊,當週邊接收到搜尋功能的廣播時就會在一定時間內回覆,在此我們可以設定一個2秒的Timer來停止scan

CBCentralManager *CM = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[CM scanForPeripheralsWithServices:nil options:options];
[NSTimer scheduledTimerWithTimeInterval:2.0f target:self selector:@selector(scanTimeout:) userInfo:nil repeats:NO];

設定2秒後觸發執行scanTimeout method,再將scanForPeripheralsWithServices的值設為nil,代表搜尋的Service type不受限制,當你只是要搜尋特定的對向時可以將它的UUID填入,像:

        NSArray	*uuidArray= [NSArray arrayWithObjects:[CBUUID UUIDWithString:@"180D"], nil];
        [CM scanForPeripheralsWithServices:uuidArray options:options];

其中「UUIDWithString:@"180D"」的180D就是Heart Rate Service type,如果有指定Service type,則結果就會將週邊有Heart Rate一一列出來,想要知道這類的Service Type可以到Bluetooth官網查詢。

一互了解Service type是哪一種類型時就可以來做對應的流程及資料的解析,也可以製作出符合標準週邊的APP。

didDiscoverPeripheral

didDiscoverPeripheral這是屬於Delegate,所以要按照它預設的宣告將要處理的過程寫在裡面,格式如下:

-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{

//處理過程

}

advertisementData會報告可以連線的週邊內容,像這樣:

adverisement:{
    kCBAdvDataLocalName = "INFOS 4090v35.05";
    kCBAdvDataServiceUUIDs =     (
        "Unknown (<fff0>)"
    );
    kCBAdvDataTxPowerLevel = 0;
}

RSSI是訊號的強度,是以NSNumber Object存在,整個範例可以是這樣子:

-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    
    NSMutableString* nsmstring=[NSMutableString stringWithString:@"\n"];
    [nsmstring appendString:@"Peripheral Info:"];
    [nsmstring appendFormat:@"NAME: %@\n",peripheral.name];
    [nsmstring appendFormat:@"RSSI: %@\n",RSSI];
    
    if (peripheral.isConnected){
        [nsmstring appendString:@"isConnected: connected"];
    }else{
        [nsmstring appendString:@"isConnected: disconnected"];
    }
    NSLog(@"adverisement:%@",advertisementData);
    [nsmstring appendFormat:@"adverisement:%@",advertisementData];
    [nsmstring appendString:@"didDiscoverPeripheral\n"];
    NSLog(@"%@",nsmstring);

}

結果輸出:

2013-02-25 14:43:17.243 gw-health-01[141:907] 
Peripheral Info:NAME: INFOS 4090v35.05
RSSI: -69
isConnected: disconnected
adverisement:{
    kCBAdvDataServiceUUIDs =     (
        "Unknown (<fff0>)"
    );
}

發現可連線的BLE週邊就會不斷的執行didDiscoverPeripheral 這個Delegate,所以要利用這個狀況將每次發現BLE週邊存入Array,再提供給USER選擇,或是從中可以去判斷某個特別的週邊是否存在而決定要不要連線。

stopScan

執行scanForPeripheralsWithServices 掃描周邊時設定一個2秒的Timer,當時間到時就停止scan,一般來說2秒內無反應就可以當作是沒有其他週邊回應,承上面scanForPeripheralsWithServices, 將stopScan寫在scanTimeout裡面:

- (void) scanTimeout:(NSTimer*)timer
{
    if (CM!=NULL){
        	[CM stopScan];
    }else{
        NSLog(@"CM is Null!");
    }
    NSLog(@"scanTimeout");
}

connectPeripheral

利用didDiscoverPeripheral所得到的BLE週邊列表讓User選擇要連線的BLE,再將 CBPeripheral傳入connectPeripheral進行連線,格式:

       [CBCentralManager connectPeripheral:CBPeripheral* options:NSDictionary*]

在此將它包裝成一個connect Method,

- (void) connect:(CBPeripheral*)peripheral
{
    
	if (![peripheral isConnected]) {
		[CM connectPeripheral:peripheral options:nil];
        connectedPeripheral=peripheral;
	}

}

option傳入nil,connectPeripheral傳入Method connect的值。

didConnectPeripheral

執行connectPeripheral之後並連線成功後就會引發didConnectPeripheral的Delegate:

-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
:
:
:
       
}

在這裡有個重點,當連線成功後引發Delegate時,就必需要針對其CBPeripheral來馬上進行discoverServices的動作,去了解週邊提供什麼樣的Services

-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"Connect To Peripheral with name: %@\nwith UUID:%@\n",peripheral.name,CFUUIDCreateString(NULL, peripheral.UUID));
    peripheral.delegate=self;
    [peripheral discoverServices:nil];//一定要執行"discoverService"功能去尋找可用的Service
    
}

執行discoverServices之後又會引發另一個Delegate叫「didDiscoverServices」,接下來則會在Explore中介紹。

Explore

BLE FLOW2.png

Discover/Connect 中使用CBCentralManager進行連線/搜尋BLE周邊的功能,連線之後需要靠的是CBPeripherall來傳送/接收資料。

CBPeripheral

使用CBPeripheral使需要設定Delegate才可以進行連線的動作,加上之前加入的CBCentralManagerDelegate,CODE就變成:

@interface DYCoreBluetooth : NSObject<CBCentralManagerDelegate, CBPeripheralDelegate> {

:
:
:
    
}

之後連線的重點全都是在Delegate的互動,查看Service Type或是有什麼樣的Services可以提供。

didConnectPeripheral

前面有稍為介紹didConnectPeripheral,這是在連線成功後就會引發的Delegate,但一定要在這裡執行一些Method才可以順利的引發另一個CBPeripheral的Delegate去查看有什麼樣的Services

-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"Connect To Peripheral with name: %@\nwith UUID:%@\n",peripheral.name,CFUUIDCreateString(NULL, peripheral.UUID));
    
    peripheral.delegate=self;
    [peripheral discoverServices:nil];//一定要執行"discoverService"功能去尋找可用的Service    
    
}

例子中已經將「 peripheral.delegate=self」,所以接下來進行 peripheral的任何動做引發的Delegate都在這個Object中,執行「discoverServices] Method,讓它去尋找Services,一找到Services就又會引發「didDiscoverServices」Delegate,這樣我們就可以了解有什麼Services。

didDiscoverServices

從這裡開始就是最關鍵

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    NSLog(@"didDiscoverServices:\n");    
    if( peripheral.UUID == NULL  ) return; // zach ios6 added    
    if (!error) {
        NSLog(@"====%@\n",peripheral.name);
        NSLog(@"=========== %d of service for UUID %@ ===========\n",peripheral.services.count,CFUUIDCreateString(NULL,peripheral.UUID));

        for (CBService *p in peripheral.services){
            NSLog(@"Service found with UUID: %@\n", p.UUID);
            [peripheral discoverCharacteristics:nil forService:p];
        }
        
    }
    else {
        NSLog(@"Service discovery was unsuccessfull !\n");
    }
    
}

peripheral.services.count 會知道有多少個Services,在每個Servces中還會有Characteristics需要了解,所以會針對每個Service來執行 peripheral discoverCharacteristics: forService:去了解每個Service下有多少個Characteristics提供傳送/接收的溝通,在執行discoverCharacteristics時也引發了didDiscoverCharacteristicsForService Delegate,最後再由didDiscoverCharacteristicsForService真正的判斷什麼樣的Service什麼樣的Characteristics再進行處理之後收到的資料,例如: 發現2A37的Characteristic,就要進行註冊通知,到時候BLE週邊發訊息過來才會在收到當下得到資料。

didDiscoverCharacteristicsForService

整個最關鍵的地方就是這個Delegate,程式架構如下:

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    
:
:
:  
    
}

Interact

BLE FLOW3.png

最後完成didDiscoverCharacteristicsForService之後,整個連線過程就算是完成,之後的didUpdateValueForCharacteristic是整個資料接收的主要Delegate,經過接收到的資料引發Delegate進行即時處理就可以取得BLE週邊的訊息,再使用writeValue的Method寫入資料到BLE週邊,整個BLE的最基本使用方式就大致上完成。

didDiscoverCharacteristicsForService

由Apple提供的資料擷取某部分來了解架構,等下程式就是利用這架構去一一尋訪所有的CharacteristicsForService

DidDiscoverCharacteristicsForServiceFrame.png

每樣Servic下都會有很多的Characteristics,Characteristics是提供資料傳遞的重點,它會有個UUID編號,再由這 個編號去Bluetooth 官方查表得到是哪種資料格式,再將資料解開加以使用。

真正的例子:

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    
    CBService *s = [peripheral.services objectAtIndex:(peripheral.services.count - 1)];
    NSLog(@"=========== Service UUID %s ===========\n",[self CBUUIDToString:service.UUID]);
    if (!error) {
        NSLog(@"=========== %d Characteristics of service ",service.characteristics.count);
        
        for(CBCharacteristic *c in service.characteristics){
            
            NSLog(@" %s \n",[ self CBUUIDToString:c.UUID]);
          //  CBService *s = [peripheral.services objectAtIndex:(peripheral.services.count - 1)];
            if(service.UUID == NULL || s.UUID == NULL) return; // zach ios6 added
            
                         
            //Register notification
            if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180D"]])
                
            {
                if ([c.UUID isEqual:[CBUUID UUIDWithString:@"2A37"]])
                {
                    [self notification:service.UUID characteristicUUID:c.UUID peripheral:peripheral on:YES];
                    NSLog(@"registered notification 2A37");
                }
                if ([c.UUID isEqual:[CBUUID UUIDWithString:@"2A38"]])
                {
                    [self notification:service.UUID characteristicUUID:c.UUID peripheral:peripheral on:YES];
                    NSLog(@"registered notification 2A38");
                }
                if ([c.UUID isEqual:[CBUUID UUIDWithString:@"2A39"]])
                {
                    [self notification:service.UUID characteristicUUID:c.UUID peripheral:peripheral on:YES];
                    NSLog(@"registered notification 2A39");
                }
                
            }
            
        }
        NSLog(@"=== Finished set notification ===\n");


    }
    else {
        NSLog(@"Characteristic discorvery unsuccessfull !\n");

    }
    if([self compareCBUUID:service.UUID UUID2:s.UUID]) {//利用此來確定整個流程都結束後才能設定通知
        [delegate didConnected:peripheral error:error];
        NSLog(@"=== Finished discovering characteristics ===\n");
        //全部服務都讀取完畢時才能使用!
        
    }

}

例子是以Heart Rate(180D)

HeartRateAssignedNumber.png

Heart Rate來說,0x2A37可以得到心跳的數據,所以針對此項可以註冊通知來得到每次的心跳數據更新。

notification是我自行撰寫的Method,可以從Service UUID及Characteristic UUID來找到Service與Characteristic的Object Point,才可以符合註冊通知的格式

    [(CBPeripheral *)p setNotifyValue:(BOOL) forCharacteristic:CBCharacteristic *)]

將Characteristic的Point傳入並設定setNotifyValue:on就完成註冊通知,之後如果有更新資料時就會引發didUpdateValueForCharacteristic Delegate,再進行資料處理。

  • notification

notification可以直接copy下程式碼重覆使用相當方便。

-(void) notification:(CBUUID *) serviceUUID characteristicUUID:(CBUUID *)characteristicUUID peripheral:(CBPeripheral *)p on:(BOOL)on {
    
    CBService *service = [self getServiceFromUUID:serviceUUID p:p];
    if (!service) {
        if (p.UUID == NULL) return; // zach ios6 addedche
        NSLog(@"Could not find service with UUID on peripheral with UUID \n");
        return;
    }
    CBCharacteristic *characteristic = [self getCharacteristicFromUUID:characteristicUUID service:service];
    if (!characteristic) {
        if (p.UUID == NULL) return; // zach ios6 added
        NSLog(@"Could not find characteristic with UUID  on service with UUID  on peripheral with UUID\n");
        return;
    }
    [p setNotifyValue:on forCharacteristic:characteristic];

}


-(CBService *) getServiceFromUUID:(CBUUID *)UUID p:(CBPeripheral *)p {
    
    for (CBService* s in p.services){
        if ([self compareCBUUID:s.UUID UUID2:UUID]) return s;
    }
    return nil; //Service not found on this peripheral
}

-(CBCharacteristic *) getCharacteristicFromUUID:(CBUUID *)UUID service:(CBService*)service {
    
    for (CBCharacteristic* c in service.characteristics){
        if ([self compareCBUUID:c.UUID UUID2:UUID]) return c;
    }
    return nil; //Characteristic not found on this service
}


didUpdateValueForCharacteristic

didUpdateValueForCharacteristic在連線完成後顯的非常重要,範例中有比對2個UUID為2A372A38

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{

    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A37"]])
    {
        if( (characteristic.value)  || !error )
        {

        }
    }
    
    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A38"]])
    {
        //set refresh int
        uint8_t val = 1;
        NSData* valData = [NSData dataWithBytes:(void*)&val length:sizeof(val)];
        [peripheral writeValue:valData forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    }
    
}

針對這兩個UUID成立時做對應的工作,這裡以2A38來解說一下。

  • 2A38

從程式中的2A38來說,當更新資料為2A38時,程式將直接寫入 1 ,也就是在下表中可以了解到,1所代表的就是Chest

HeartRate 2A38.png

意思是告訴心跳感測器量測的位置是在胸部的部分。

<think>好的,用户现在想了解CoreBluetooth框架在iOS 18上的兼容性问题及解决方法。首先,我需要确认目前iOS 18的发布状态,是否已经正式推出还是处于测试阶段。因为如果是测试版的话,可能存在一些已知的问题和临时的解决方案。 根据现有的引用内容,CoreBluetooth是iOS中用于蓝牙通信的框架,涉及到权限描述如NSBluetoothAlwaysUsageDescription。用户之前可能遇到过因为权限描述缺失导致提交App Store失败的问题,所以现在可能关心升级到iOS 18后是否有新的权限要求或API变动。 接下来,我需要查阅苹果的开发者文档,看看iOS 18对CoreBluetooth有哪些更新。可能的变动包括新功能的添加、旧API的废弃、权限变更等。例如,是否引入了新的隐私权限,需要更新Info.plist中的描述字段,或者是否有API的行为发生了变化,导致现有代码需要调整。 另外,考虑到第三方库如react-native-bluetooth-classic可能依赖CoreBluetooth,需要检查这些库是否有适配iOS 18的更新版本。如果旧版本依赖了被废弃的方法,可能需要升级库或修改代码。 用户可能还关心在Xcode中如何检测兼容性问题,比如使用最新的Xcode beta版进行编译和测试,利用编译器警告和运行时日志来发现潜在问题。此外,测试蓝牙功能在iOS 18设备上的实际表现,例如设备发现、连接稳定性、数据传输等,是否存在异常。 还需要考虑向后兼容性,确保在支持旧版本iOS的同时适配iOS 18的新特性。比如使用@available条件编译来区分不同系统版本的API调用。 最后,整理出具体的兼容性问题和对应的解决方案,以清晰的步骤呈现给用户,帮助他们顺利过渡到iOS 18环境。</think>关于CoreBluetooth框架在iOS 18上的兼容性问题及解决方案,根据现有技术文档和开发者反馈,以下是关键要点: ### 一、已知兼容性问题 1. **蓝牙权限变更** iOS 18要求更严格的蓝牙权限控制,未正确配置`NSBluetoothAlwaysUsageDescription`的App可能首次调用API时崩溃。这与iOS 17的行为差异在于,iOS 18已强制要求显示权限弹窗[^1]。 2. **后台模式限制** 使用`CBCentralManager`进行后台蓝牙扫描时,iOS 18新增限制条件:必须启用`Uses Bluetooth LE accessories`后台模式且设备处于充电状态[^2]。 3. **API弃用警告** 使用Xcode 16编译时,以下方法会提示警告: ```swift CBCentralManager.init(delegate: queue: options:) // 建议改用CBCentralManagerDelegateWithQueue ``` ### 二、解决方案 1. **权限配置强化** 在Info.plist中增加双权限描述(推荐同时配置): ```xml <key>NSBluetoothAlwaysUsageDescription</key> <string>需要蓝牙权限用于设备连接</string> <key>NSBluetoothPeripheralUsageDescription</key> <string>需要蓝牙权限用于配件配对</string> ``` 2. **API适配方案** 针对弃用API的修改示例: ```swift // 旧代码 let manager = CBCentralManager(delegate: self, queue: nil) // 新代码(iOS 18推荐) let manager = CBCentralManager() manager.delegateWithQueue = (self, DispatchQueue.main) ``` 3. **后台扫描优化** 添加以下Capabilities并修改扫描参数: ```swift let options: [String: Any] = [ CBCentralManagerScanOptionAllowDuplicatesKey: false, CBCentralManagerScanOptionRequiresActiveAccessoryKey: true // 新增iOS 18专用参数 ] centralManager.scanForPeripherals(withServices: nil, options: options) ``` ### 三、验证方法 1. 在Xcode 16中使用**Bluetooth诊断工具**: ```bash xcrun bluetoothd --debug ``` 2. 开启`ENABLE_BLUETOOTH_LOGGING`编译标识获取详细日志 ### 四、第三方库适配 若使用react-native-bluetooth-classic等跨平台库: 1. 升级到v0.7.0+版本 2. 在Podfile添加post_install钩子: ```ruby post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0' end end end ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值