顧名思義怒竿,就是本機搜索其他藍(lán)牙設(shè)備進行讀寫砍鸠。
CBCentralManager
對象表示中心設(shè)備,用于管理已發(fā)現(xiàn)和已連接的外圍設(shè)備耕驰。包括發(fā)現(xiàn)外圍設(shè)備爷辱、發(fā)起連接、斷開連接朦肘。
CBPeripheral
對象表示外圍設(shè)備饭弓。我們要獲取數(shù)據(jù)就需要處理CBPeripheral
對象中的服務(wù)(CBService
)和特征(CBCharacteristic
)

CBCentralManagerDelegate協(xié)議部分
1. 創(chuàng)建中心設(shè)備管理對象
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// queue 可以為nil,就在主線程中工作
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue options:nil];
創(chuàng)建對象后會調(diào)用下面的代理方法媒抠,可以在其中檢查藍(lán)牙的狀態(tài)
/**
創(chuàng)建好中心設(shè)備對象后的回調(diào)弟断,確定設(shè)備是否支持藍(lán)牙以及是否可用
* @constant CBManagerStateUnknown 未知狀態(tài),立即刷新.
* @constant CBManagerStateResetting 重置藍(lán)牙趴生,鏈接暫時斷開阀趴,立即刷新.
* @constant CBManagerStateUnsupported 設(shè)備不支持藍(lán)牙4.0 .
* @constant CBManagerStateUnauthorized 應(yīng)用尚未被授權(quán).
* @constant CBManagerStatePoweredOff 藍(lán)牙處于關(guān)閉狀態(tài).
* @constant CBManagerStatePoweredOn 藍(lán)牙處于開啟狀態(tài).
*/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
// NS_ENUM形式的枚舉昏翰,使用 == 判斷
if (CBManagerStatePoweredOn == central.state) {
// do something 可以開始掃描什么的
[self.centralManager scanForPeripheralsWithServices:nil options:nil]
}
}
2. 搜尋外圍設(shè)備
// 搜索給定UUID服務(wù)的外圍設(shè)備,nil表示搜索所有的
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
options
是一個字典刘急,指定兩個選項
-
CBCentralManagerScanOptionAllowDuplicatesKey
:默認(rèn)為NO(需要用NSNumber包裝)棚菊,表示不會重復(fù)掃描已發(fā)現(xiàn)藍(lán)牙設(shè)備,否則會增加耗電叔汁。但是筆者在實現(xiàn)時沒發(fā)現(xiàn)有什么不同统求,都在不停掃。o(╯□╰) 關(guān)閉藍(lán)牙重新連接就滿足要求了 o(╯□╰)o -
CBCentralManagerScanOptionSolicitedServiceUUIDsKey
:value是一個包含指定UUID服務(wù)的數(shù)組据块。
當(dāng)掃描到一個外圍設(shè)備時码邻,觸發(fā)代理。提升peripheral
為屬性以便之后繼續(xù)使用另假。
/**
搜尋到外圍設(shè)備時調(diào)用冒滩,發(fā)現(xiàn)一個調(diào)用一次
@param advertisementData 掃描時收到的廣告包
@param RSSI 信號強度,一個負(fù)數(shù)
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
switch (peripheral.state) {
case CBPeripheralStateDisconnected:
NSLog(@"鏈接斷開");
break;
case CBPeripheralStateConnecting:
NSLog(@"鏈接ing");
break;
case CBPeripheralStateConnected:
NSLog(@"鏈接建立");
// 找到想要鏈接的設(shè)備時浪谴,應(yīng)該終止搜索以節(jié)省電量。
[self.centralManager stopScan];
break;
case CBPeripheralStateDisconnecting:
NSLog(@"鏈接正在斷開");
break;
default:
break;
}
// 連接設(shè)備
[self.centralManager connectPeripheral:peripheral options:nil];
}
Tip
:
- 除非特殊情況(如需要根據(jù)信號強度來連接設(shè)備等)不對已發(fā)現(xiàn)設(shè)備重復(fù)掃描因苹。
// 因為該key默認(rèn)是NO苟耻,所以可以將 options 設(shè)為 nil
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey: [NSNumber numberWithBool:NO]}];
- 由于中心設(shè)備開啟掃描后不會自動停止,所以蘋果推薦在匹配到合適設(shè)備時主動停止扶檐⌒渍龋可以通過
CBPeripheral
的屬性name
或者identifier(NSUUID *)
來確定我們需要的設(shè)備。一旦確定就可以調(diào)用[self.centralManager stopScan]
終止掃描款筑。
3. 連接外圍設(shè)備
// 連接設(shè)備
[self.centralManager connectPeripheral:peripheral options:nil];
遵循協(xié)議CBPeripheralDelegate
智蝠,實現(xiàn)對數(shù)據(jù)的讀寫
/**
鏈接外圍設(shè)備成功時調(diào)用,此時可以開始對外圍設(shè)備進行讀寫了
*/
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
// 設(shè)定外圍設(shè)備的代理
peripheral.delegate = self;
[peripheral discoverServices:nil];
}
CBPeripheralDelegate協(xié)議部分
4. 獲取外圍設(shè)備可用服務(wù)
// nil 會搜索所有服務(wù)
[peripheral discoverServices:nil]
成功發(fā)現(xiàn)服務(wù)時奈梳,調(diào)用代理
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
// 所有可用服務(wù)都包含在其中
for (CBService *service in peripheral.services) {
// 180A 表示 Device infomation
if ([service.UUID.UUIDString isEqualToString:@"180A"]) {
[peripheral discoverCharacteristics:nil forService:service];
}
}
}
5. 獲取服務(wù)的特征
// 獲取指定服務(wù)的特征, nil 獲取所有
[peripheral discoverCharacteristics:nil forService:service]
獲取成功后杈湾,調(diào)用代理
/**
發(fā)現(xiàn)特征時調(diào)用,調(diào)用一次
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
// 包含所有特征
for (CBCharacteristic *characteristic in service.characteristics) {
// 讀取特征值
}
}
Tip
:因為一個外圍設(shè)備有多個服務(wù)攘须,一個服務(wù)又有多個特征漆撞,而一般我們只需要一部分服務(wù)或特征。所以我們在獲取服務(wù)或特征時于宙,通過UUID僅得到我們需要的即可浮驳。
// nil 會搜索所有服務(wù),但是耗時耗能捞魁,所以盡量傳入服務(wù)uuid
// 獲取服務(wù)
[peripheral discoverServices:數(shù)組<CBUUID *>];
// 獲取指定服務(wù)的特征
[peripheral discoverCharacteristics:數(shù)組<CBUUID *> forService:service];
6. 讀寫操作
特征中就含有我們需要讀寫的數(shù)據(jù)至会。注意不是所有特征值都可讀寫,依賴于外圍設(shè)備對特征的配置谱俭。通過CBCharacteristic
的屬性properties
是否包含以下枚舉值來判斷是否可讀寫奉件。這是一個CBCharacteristicProperties
類型的枚舉宵蛀,NS_OPTIONS
表示枚舉值可以組合。
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
// 特征的值通過特征的描述符被廣播出去
CBCharacteristicPropertyBroadcast = 0x01,
// 可讀
CBCharacteristicPropertyRead = 0x02,
// 可寫瓶蚂,外圍設(shè)備不會回傳寫入是否成功
CBCharacteristicPropertyWriteWithoutResponse = 0x04,
// 可寫糖埋,外圍設(shè)備告知中心設(shè)備寫入是否成功,然后CBPeripheral調(diào)用相應(yīng)代理
CBCharacteristicPropertyWrite = 0x08,
// 可訂閱,中心設(shè)備是否收到通知不會告知外圍設(shè)備
CBCharacteristicPropertyNotify = 0x10,
// 可訂閱,中心設(shè)備是否收到通知會告知外圍設(shè)備窃这,然后CBCentral調(diào)用相應(yīng)代理
CBCharacteristicPropertyIndicate = 0x20,
// 寫入不成功時不會回傳錯誤瞳别??
CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
// 可以在特征的屬性描述符中添加額外的特征屬性
CBCharacteristicPropertyExtendedProperties = 0x80,
// 只能訂閱受信任設(shè)備廣播的特征的值杭攻,不會告知外圍設(shè)備是否收到通知
CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x100,
// 只能訂閱受信任設(shè)備廣播的特征的值祟敛,會告知外圍設(shè)備是否收到通知
CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x200
};
讀操作
通過直接讀取或訂閱的方式獲取特征的值。
- 直接讀取
// NS_OPTIONS形式的枚舉兆解,使用 & 判斷是否包含
if (characteristic.properties & CBCharacteristicPropertyRead) {
[peripheral readValueForCharacteristic:characteristic];
}
- 通過訂閱的方式(推薦)馆铁,類似觀察者
一次值讀取就是一次交互,就要使用設(shè)備信號锅睛。如果一個特征的值可能變化可能不變時埠巨,直接讀取就需要不停調(diào)用readValueForCharacteristic:
方法。當(dāng)一段時間內(nèi)數(shù)據(jù)保持不變现拒,使用readValueForCharacteristic:
方法就會造成不必要的數(shù)據(jù)交互從而消耗電池辣垒。
如何避免呢?最好就是當(dāng)特征的值變化時印蔬,外圍設(shè)備通知相應(yīng)的中心設(shè)備勋桶,中心設(shè)備收到通知在去讀取值。
如何做到侥猬?使用訂閱例驹。
// 動態(tài)變化值使用通知訂閱更高效
// 外圍設(shè)備特征的值改變時,發(fā)出通知退唠,訂閱了該特征值的中心設(shè)備收到通知后更新值
// 不是所有特征值都可訂閱鹃锈,最好判斷一下
if (characteristic.properties & CBCharacteristicPropertyNotify) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
啟用訂閱后,調(diào)用代理
/**
外圍設(shè)備收到啟用或停止通知請求后會調(diào)用
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
}
讀取或訂閱的特征的值改變后瞧预,調(diào)用代理
/**
特征值改變時調(diào)用, 兩種讀取方式都要走該代理
@param error 讀取特征值失敗時
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
// 得到特征值
NSData *data = characteristic.value;
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
寫操作
寫入數(shù)據(jù)時以二進制數(shù)據(jù)操作仪召,需要使用NSData
包裝。
一個特征是否可寫入及寫入方式松蒜,可通過properties
是否包含CBCharacteristicPropertyWriteWithoutResponse
或CBCharacteristicPropertyWrite
確定扔茅。
if (characteristic.properties & CBCharacteristicPropertyWrite) {
[peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
CBCharacteristicWriteWithResponse
和CBCharacteristicWriteWithoutResponse
,前者寫入數(shù)據(jù)后調(diào)用下面的代理方法秸苗,告知我們寫入是否成功召娜。后者不會調(diào)用。
/**
使用 CBCharacteristicWriteWithResponse 寫入方式才調(diào)用該代理方法
寫入失敗惊楼,則會通過error提示錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
}
7. 斷開連接
由于藍(lán)牙廣播或掃描時玖瘸,使用的是設(shè)備信號秸讹。而設(shè)備信號也用于WIFI等其他功能。同時信號強度又會影響電池使用雅倒。為減小影響璃诀,需要最小化對信號的使用。
當(dāng)中心設(shè)備訂閱的特征不再發(fā)送通知后(CBPeripheral
對象的isNotifying
屬性)或者 我們已經(jīng)拿到所有需要的數(shù)據(jù)后蔑匣,應(yīng)該及時取消訂閱然后斷開和外圍設(shè)備的連接劣欢。
// 為每一個訂閱過的特征 設(shè)置 值 為 NO
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
// 僅斷開當(dāng)前app與外圍設(shè)備的連接,不會關(guān)閉藍(lán)牙裁良。因為可能有其他app也在使用藍(lán)牙
[self.centralManager cancelPeripheralConnection:self.peripheral];
斷開連接后凿将,調(diào)用代理
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"disconnect error: %@", error.userInfo);
// 異常斷開,可以做重連
}
8. 重新連接
有三種重新獲取外圍設(shè)備的方式:
- 所需外圍設(shè)備僅和本地app斷連价脾,但并未和中心設(shè)備斷開連接牧抵,則應(yīng)使用服務(wù)(
CBUUID *
)嘗試獲取CBPeripheral
。成功 array 就不為空侨把,從中選擇需要的peripheral
犀变。否則嘗試后面兩種方法。
NSArray *array = [central retrieveConnectedPeripheralsWithServices:數(shù)組<CBUUID *>];
- 本機保存(
NSUserDefault
等)著連接過的外圍設(shè)備的(identifier(NSUUID*
)秋柄,通過它嘗試獲取CBPeripheral
获枝。成功 array 就不為空,從中選擇需要的 peripheral华匾。否則嘗試重新掃描。
NSArray *array = [central retrievePeripheralsWithIdentifiers:數(shù)組<NSUUID *>];
- 通過以上方式都未能重新連接机隙,就只有重新掃描
scanForPeripheralsWithServices: options:
之后調(diào)用代理
centralManager: didDiscoverPeripheral: advertisementData: RSSI:
就能獲取掃描到的peripheral
通過以上三種方式獲取到需要的peripheral
后仍然使用connectPeripheral: options:
請求重新連接蜘拉,最后調(diào)用相應(yīng)代理。