iOS BLE藍(lán)牙單例封裝

公司是一家做藍(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)點沪猴,可是奈何我老花眼辐啄,實在查不出來,遇到錯誤留言告知运嗜,謝謝壶辜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市洗出,隨后出現(xiàn)的幾起案子士复,更是在濱河造成了極大的恐慌,老刑警劉巖翩活,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異便贵,居然都是意外死亡菠镇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門承璃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來利耍,“玉大人,你說我怎么就攤上這事盔粹“妫” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵舷嗡,是天一觀的道長轴猎。 經(jīng)常有香客問我,道長进萄,這世上最難降的妖魔是什么捻脖? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮中鼠,結(jié)果婚禮上可婶,老公的妹妹穿的比我還像新娘。我一直安慰自己援雇,他們只是感情好矛渴,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惫搏,像睡著了一般具温。 火紅的嫁衣襯著肌膚如雪蚕涤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天桂躏,我揣著相機(jī)與錄音钻趋,去河邊找鬼。 笑死剂习,一個胖子當(dāng)著我的面吹牛蛮位,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鳞绕,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼失仁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了们何?” 一聲冷哼從身側(cè)響起萄焦,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冤竹,沒想到半個月后拂封,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鹦蠕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年冒签,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钟病。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡萧恕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肠阱,到底是詐尸還是另有隱情票唆,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布屹徘,位于F島的核電站走趋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缘回。R本人自食惡果不足惜吆视,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酥宴。 院中可真熱鬧啦吧,春花似錦、人聲如沸拙寡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至般堆,卻和暖如春在孝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背淮摔。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工私沮, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人和橙。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓仔燕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親魔招。 傳聞我的和親對象是個殘疾皇子晰搀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • iOS開發(fā)藍(lán)牙4.0初識轉(zhuǎn)載 2015-09-20 15:26:44標(biāo)簽:ios開發(fā)藍(lán)牙ios開發(fā)藍(lán)牙4.0ios...
    Jany_4a9a閱讀 2,716評論 0 3
  • 初識低功耗藍(lán)牙 Android 4.3(API Level 18)開始引入Bluetooth Low Energy...
    JBD閱讀 112,559評論 46 342
  • 有緣同行閱讀 264評論 0 3
  • 讀了史鐵生的我與地壇,想將其中的一段話和大家一起分享“一個人办斑,出生了外恕,這就不再是一個可以辯論的問題,而只是上帝交給...
    魚耗子閱讀 240評論 0 0
  • 雙手合十 感恩 發(fā)現(xiàn)自己心里好空虛 做事好猶豫 沒有明確的目標(biāo) 想啥都做 可啥也都做不過來 好想有朋友 有...
    姜小禪閱讀 173評論 0 0