公司是一家做藍(lán)牙只能硬件的公司惧盹,所以整理一些藍(lán)牙開發(fā)的筆記。
一個好用的第三方藍(lán)牙庫:BabyBluetooth
這個庫對藍(lán)牙進(jìn)行了一些封裝,提供了豐富的接口峡钓,基本可以滿足藍(lán)牙的很多操作,大家可以去學(xué)習(xí)一下槐雾。
但是由于公司要求夭委,這里只能使用原生的藍(lán)牙框架<CoreBluetooth/CoreBluetooth.h>,廢話不多說募强,開始吧株灸。PS.這里是將手機(jī)當(dāng)做中央,設(shè)備作為周邊的實現(xiàn)擎值。
為什么要用單例慌烧?
首先大家來思考這個問題:為什么我們要用單例來封裝藍(lán)牙模塊呢?
如果大家開發(fā)藍(lán)牙的話就一定知道鸠儿,我們會有很多頁面需要用到連接屹蚊,或者是寫入相應(yīng)的數(shù)據(jù),所以這個時候我們就想到了一個單例进每,將藍(lán)牙模塊封裝到單例里去汹粤,就可以實現(xiàn)在每個控制器中都可以拿到唯一的藍(lán)牙對象,從而保證藍(lán)牙連接的穩(wěn)定性田晚。
在這里吐個槽嘱兼,在剛來公司時,接手了一個項目贤徒,讓我改進(jìn)里面的藍(lán)牙功能芹壕,拿到手里整個傻眼,我類個去接奈,每個用到藍(lán)牙的地方都寫了一整套藍(lán)牙協(xié)議的方法踢涌,臃腫度令人發(fā)指,而且導(dǎo)致再切換不同的頁面會走不同的藍(lán)牙協(xié)議方法鲫趁,導(dǎo)致藍(lán)牙連接的不穩(wěn)定斯嚎。(=、=讓我做一秒怨婦)挨厚。
創(chuàng)建單例
由于看我的文章的有一部分是基礎(chǔ)不是很好的(其實我基礎(chǔ)也不好o(╯□╰)o)堡僻,這里簡單提一下單例的創(chuàng)建。
@interface BLETool () <CBCentralManagerDelegate,CBPeripheralDelegate>
@property (nonatomic ,strong) NSMutableArray *deviceArr;
//中央設(shè)備的屬性疫剃,全部操作都是通過這個來
@property (nonatomic ,strong)CBCentralManager *myCentralManager;
@end
#pragma mark - Singleton
static BLETool *bleTool = nil;
- (instancetype)init
{
self = [super init];
if (self) {
//這里centralManager需要設(shè)置CBCentralManagerDelegate,CBPeripheralDelegate這兩個代理
_myCentralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil options:nil];
}
return self;
}
+ (instancetype)shareInstance
{
//這里是通過GCD的once方法實現(xiàn)單例的創(chuàng)建钉疫,該方法在整個應(yīng)用程序中只會執(zhí)行一次,所以經(jīng)常用作單例的創(chuàng)建巢价。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
bleTool = [[self alloc] init];
});
return bleTool;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
bleTool = [super allocWithZone:zone];
});
return bleTool;
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
return self;
}
#pragma mark - 懶加載
- (NSMutableArray *)deviceArr
{
if (!_deviceArr) {
_deviceArr = [NSMutableArray array];
}
return _deviceArr;
}
我們通過shareInstance的方法牲阁,去創(chuàng)建BLETool的單例對象固阁,去執(zhí)行所有的掃描,連接城菊,寫入特征等等操作备燃。
掃描設(shè)備
由于我們需要在外界的控制器中通過BLETool這個單例來進(jìn)行所有的操作,所以這里我們需要在BLETool的.h文件中給出方法接口凌唬。
@interface BLETool : NSObject
+ (instancetype)shareInstance;
//可保存當(dāng)前連接的設(shè)備并齐,根據(jù)需要放在.h或者.m文件中
@property (nonatomic ,strong) CBPeripheral *currentPer;
#pragma mark - action of connecting layer -連接層操作
//掃描設(shè)備
- (void)scanDevice;
//停止掃描
- (void)stopScan;
//連接設(shè)備
- (void)connectDevice:(CBPeripheral *)peripheral;
//斷開設(shè)備連接
- (void)unConnectDevice;
//重連設(shè)備
- (void)reConnectDevice:(BOOL)isConnect;
//檢索已連接的外接設(shè)備
- (NSArray *)retrieveConnectedPeripherals;
好了,我們已經(jīng)向外界提供了一些常用的接口客税,做一些藍(lán)牙的普通操作等况褪,下面來看看這些方法的實現(xiàn)過程:
#pragma mark - action of connecting layer -連接層操作
- (void)scanDevice
{
[self.deviceArr removeAllObjects];
//這里的第一個參數(shù)設(shè)置為nil,就掃描所有的設(shè)備更耻,如果只想返回特定的服務(wù)的設(shè)備测垛,就給服務(wù)的數(shù)組
[_myCentralManager scanForPeripheralsWithServices:nil options:nil];
}
- (void)stopScan
{
[_myCentralManager stopScan];
}
- (void)connectPeripheral:(CBPeripheral *)peripheral
{
self.currentPer = peripheral;
//請求連接到此外設(shè)
[_myCentralManager connectPeripheral:peripheral options:nil];
}
- (void)unConnectPeripheral
{
[self.myCentralManager cancelPeripheralConnection:self.currentPer];
}
- (NSArray *)retrieveConnectedPeripherals
{
//這里值得注意,在ios9.0之前秧均,可以用retrieveConnectedPeripherals這個方法來返回手機(jī)已經(jīng)連接的設(shè)備食侮,
//但是在9.0被廢棄了,現(xiàn)在需要特定的服務(wù)UUID才能返回特定的已連接設(shè)備熬北。
return [_myCentralManager retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
}
在我們調(diào)用了scanDevice的方法
[[BLETool shareInstance] scanDevice];
就會觸發(fā)BLETool的代理方法:
#pragma mark - CBCentralManagerDelegate
//檢查設(shè)備藍(lán)牙開關(guān)的狀態(tài)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state == CBCentralManagerStatePoweredOn) {
NSLog(@"藍(lán)牙已打開");
[_myCentralManager scanForPeripheralsWithServices:nil options:nil];
}else {
NSLog(@"藍(lán)牙已關(guān)閉");
}
}
//查找到正在廣播的外設(shè)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"Discovered %@", peripheral.name);
//當(dāng)你發(fā)現(xiàn)你感興趣的外圍設(shè)備疙描,停止掃描其他設(shè)備,以節(jié)省電能讶隐。
if (![self.deviceArr containsObject:peripheral]) {
[self.deviceArr addObject:peripheral];
}
}
這里我們掃描到了一些外設(shè)起胰,并將他們保存在了self.deviceArr數(shù)組中,現(xiàn)在我們需要將這些掃描到的設(shè)備傳遞給需要的控制器巫延,由于后面需要用到很多的返回數(shù)據(jù)效五,所以這里我們考慮使用代理來完成。在BLETool.h中設(shè)置協(xié)議和協(xié)議方法:
//掃描設(shè)備協(xié)議
@protocol BleDelegate <NSObject>
@required
- (void)BLEDidDiscoverDeviceWithMAC:(CBPeripheral *)peripheral;
@end
協(xié)議設(shè)置好了后炉峰,在剛剛掃描設(shè)備的方法中調(diào)用:
//查找到正在廣播的外設(shè)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"Discovered %@", peripheral.name);
//當(dāng)你發(fā)現(xiàn)你感興趣的外圍設(shè)備畏妖,停止掃描其他設(shè)備,以節(jié)省電能疼阔。
if (![self.deviceArr containsObject:peripheral]) {
[self.deviceArr addObject:peripheral];
if ([self.discoverDelegate respondsToSelector:@selector(manridyBLEDidDiscoverDeviceWithMAC:)]) {
//返回掃描到的設(shè)備實例
[self.discoverDelegate manridyBLEDidDiscoverDeviceWithMAC:device];
}
}
}
OK~戒劫!掃描完成,我們?nèi)バ枰目刂破骼锶ソ邮瞻善爬龋≡谛枰B接的控制器里設(shè)置代理迅细,并實現(xiàn)代理方法:
設(shè)置代理
注意這里設(shè)置代理時要使用weak來做delegate的修飾,這樣才不會導(dǎo)致強引用循環(huán)淘邻,具體可以去看看這篇文章茵典。
[BLETool shareInstance].discoverDelegate = self;
實現(xiàn)方法
#pragma mark - BleDelegate
- (void)BLEDidDiscoverDeviceWithMAC:(CBPeripheral *)peripheral
{
[self.deviceArr addObject:peripheral];
//這里我用了一個TableView去展示掃描到的設(shè)備宾舅,可以使用peripheral.name來作為設(shè)備展示
NSLog(@"有%ld個設(shè)備:%@",(unsigned long)self.deviceArr.count,self.deviceArr);
[self.deviceTableView reloadData];
}
至此設(shè)備掃描部分就完成了统阿,簡單提一下停止掃描吧彩倚,這個可以自己定義,當(dāng)掃描5秒后就停止掃描以節(jié)約電量扶平。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[BLETool shareInstance] stopScan];
});
連接設(shè)備
掃描完設(shè)備之后帆离,我們可以在需要的地方進(jìn)行展示,如同上面實現(xiàn)方法中蜻直,我用到了一個UITableView去展示所有掃描到的設(shè)備盯质,然后可以通過點擊,相應(yīng)的cell概而,去連接對應(yīng)的設(shè)備。這個時候就需要用到我們連接層提供的接口:
- (void)connectPeripheral:(CBPeripheral *)peripheral
當(dāng)然這里需要將你需要連接的設(shè)備傳過去囱修,比如我們在UITableView的cell的點擊方法中就可以執(zhí)行這個連接方法:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[[BLETool shareInstance] connectDevice:self.deviceArr[indexPath.row]];
self.currentDevice = self.deviceArr[indexPath.row];
}
此時赎瑰,會調(diào)用BLETool的連接方法:
- (void)connectPeripheral:(CBPeripheral *)peripheral
{
self.currentPer = peripheral;
//請求連接到此外設(shè)
[_myCentralManager connectPeripheral:peripheral options:nil];
}
注意,這里建議將接受到的要連接的外設(shè):peripheral 給保存下來破镰,以便后面會用到餐曼。接下來,如果連接成功鲜漩,會走 CBCentralManagerDelegate 里面的連接成功的回調(diào):
//連接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"成功連接上設(shè)備:%@", peripheral.name);
peripheral.delegate = self;
//傳入nil會返回所有服務(wù);一般會傳入你想要服務(wù)的UUID所組成的數(shù)組,就會返回指定的服務(wù)
[peripheral discoverServices:nil];
}
這里如果連接成功的話源譬,我們就需要執(zhí)行發(fā)現(xiàn)服務(wù)的方法了,這里需要傳遞一個服務(wù) UUID 的參數(shù)孕似,如果傳nil的話踩娘,就會返回這個設(shè)備的所有的服務(wù)(Service),如果你知道你需要的是哪個服務(wù)喉祭,可以傳入該服務(wù)的 UUID 养渴,這樣就會返回指定的服務(wù)了。
如果連接失敗了泛烙,則會回調(diào) * CBCentralManagerDelegate* 里面的連接失敗的回調(diào):
//連接失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"連接失敗");
}
你可以在這個方法里面做一些連接失敗的操作理卑,比如提示用戶重新連接之類。這里提一下斷開連接蔽氨,斷開連接的情況我分析為兩種:第一種藐唠,由于藍(lán)牙設(shè)備的傳輸距離有限,一般有效距離為十米鹉究,這就產(chǎn)生了一單設(shè)備離開手機(jī)超過十米宇立,可能就斷開連接了;另外還有一種情況坊饶,就是很多時候我們在APP中需要給用戶一個手動去斷開藍(lán)牙連接的情況泄伪,比如點擊按鈕斷開藍(lán)牙。這兩種情況概況為 主動斷開 和 被動斷開匿级,主動斷開我們只需要調(diào)用BLETool里面的斷開連接的接口:
- (void)unConnectPeripheral
然而被動斷開的情況蟋滴,我們可能會希望在設(shè)備回到可被連接的范圍內(nèi)后染厅,由APP自動幫我們重新連接。這樣的情況津函,就在我們斷開連接的回調(diào)中做如下操作:
//斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
if (如果是被動斷開肖粮,需要重連) {
[self.myCentralManager connectPeripheral:self.currentPer options:nil];
}else {
NSLog(@"斷開成功,不需要斷線重連");
//這里執(zhí)行的步驟是斷開連接成功的回調(diào)尔苦,在需要做操作的類里面去實現(xiàn)這個方法就行
if ([self.connectDelegate respondsToSelector:@selector(manridyBLEDidDisconnectDevice:)]) {
[self.connectDelegate manridyBLEDidDisconnectDevice:self.currentPer];
}
self.currentDev = nil;
}
}
這里我們可以看到涩馆,如果是被動斷開的情況(這里是否為被動,可以通過外類傳進(jìn)來允坚,自行解決)魂那,我們只需要重新執(zhí)行連接的方法即可,前提是稠项,這里的 self.currentPer 要與之前連接的設(shè)備保持一致涯雅,這樣才能完成斷線重連的操作;而如果使我們主動斷開的情況的話展运,只需要做個斷開成功的回調(diào)活逆,讓需要斷開連接的類知道即可,最后斷開連接后拗胜,最好將 self.currentPer 置為nil蔗候,以便下一次連接使用。
以上基本就是連接與斷開的過程了埂软,接下來就是服務(wù)與特征锈遥,也是藍(lán)牙開發(fā)中,比較重要的一部分仰美。
服務(wù)
上面連接成功的回調(diào)里迷殿,我們執(zhí)行了 discoverServices 這個方法,并且返回了這個設(shè)備所有的服務(wù)咖杂,這些服務(wù)都在 CBPeripheralDelegate 協(xié)議方法中進(jìn)行了返回:
#pragma mark - CBPeripheralDelegate
//發(fā)現(xiàn)到服務(wù)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service %@",service.UUID);
//返回特定的服務(wù)庆寺,訂閱的特征即可
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kWriteCharacteristicUUID],[CBUUID UUIDWithString:kNotifyCharacteristicUUID]] forService:service];
}
}
這個回調(diào)中,會將設(shè)備的服務(wù)返回給我們诉字,只需要在這里面去發(fā)現(xiàn)我們所需要的特征值即可懦尝,如 kWriteCharacteristicUUID 這個特征值,是我宏定義的寫入特征值壤圃,而 ** kNotifyCharacteristicUUID**是我需要訂閱的特征值陵霉。這主要需要底層那邊告訴你這個特征值即可,接下來伍绳,需要在發(fā)現(xiàn)特征值得方法里去訂閱我們需要的特征值(好多回調(diào)=踊挠、=!頭都暈了。效床。睹酌。):
//獲得某服務(wù)的特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"Discovered characteristic %@", characteristic.UUID);
//保存寫入特征
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kWriteCharacteristicUUID]]) {
self.currentDev.writeCharacteristic = characteristic;
}
//保存訂閱特征
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kNotifyCharacteristicUUID]]) {
self.currentDev.notifyCharacteristic = characteristic;
//訂閱該特征
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
有點晚了,暫時更新這么多剩檀,后面會介紹一些特征值寫入和更新的內(nèi)容憋沿。
ps.由于是手寫的代碼,很多地方標(biāo)點可能是中文標(biāo)點沪猴,可是奈何我老花眼辐啄,實在查不出來,遇到錯誤留言告知运嗜,謝謝壶辜。