作為多年的iOS藍(lán)牙開發(fā)者,下面共享一下我自己搭建和使用的藍(lán)牙框架飒箭。首先創(chuàng)建一個(gè)類BluetoothManager,用來管理CBCentralManager和CBPeripheral蜒灰,以及一些掃描連接的公共方法弦蹂,其中數(shù)據(jù)和狀態(tài)回調(diào)使用通知的形式進(jìn)行處理,這樣在整個(gè)項(xiàng)目中强窖,只需要保存一份BluetoothManager就可以了凸椿,而不是在每個(gè)需要藍(lán)牙的頁面都生成CBCentralManager。
BluetoothManager.h文件實(shí)現(xiàn):
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// 通知回調(diào)翅溺,需要接受消息的類必須注冊對應(yīng)的通知
static NSString * const BLENotificationBLECenterChangeState = @"BLENotificationBLECenterChangeState"; // 藍(lán)牙狀態(tài)改變
static NSString * const BLENotificationPeripheralDidConnected = @"BLENotificationPeripheralDidConnected"; // 設(shè)備連接成功
static NSString * const BLENotificationPeripheralConnectFailed = @"BLENotificationPeripheralConnectFailed"; // 設(shè)備連接失敗
static NSString * const BLENotificationPeripheralDisconnected = @"BLENotificationPeripheralDisconnected"; // 設(shè)備斷開連接
static NSString * const BLENotificationPeripheralDidSendValue = @"BLENotificationPeripheralDidSendValue"; // 寫入設(shè)備數(shù)據(jù)
static NSString * const BLENotificationPeripheralReceiveValue = @"BLENotificationPeripheralReceiveValue"; // 收到設(shè)備數(shù)據(jù)
static NSString * const BLENotificationPeripheralSessionError = @"BLENotificationPeripheralSessionError"; // 設(shè)備會(huì)話異常
static NSString * const BLENotificationPeripheralReadRSSIValue = @"BLENotificationPeripheralReadRSSIValue"; // 設(shè)備信號(hào)強(qiáng)度
/////////////////////////////////////////////////////////////////////////////////////////////////////////
static NSString * const BLEUUIDServicesSession = @"1234"; // 通信Services
static NSString * const BLEUUIDCharacteristicRead = @"CF16"; // 讀特征值
static NSString * const BLEUUIDCharacteristicWrite = @"00000000-0000-0000-0000-000000123456"; // 寫特征值
static NSString * const BLEUUIDCharacteristicNotify = @"00000000-0000-0000-0000-000000563412"; // 通知特征值
/////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef void (^ScanBlock)(CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI);
typedef void (^WriteBlock)(BOOL success);
typedef void (^TimeoutBlock)(void);
/////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface BluetoothManager : NSObject
@property (strong, nonatomic) CBCentralManager *central;
@property (strong, nonatomic) CBPeripheral *peripheral; // 當(dāng)前已連接上的設(shè)備
@property (strong, nonatomic) CBPeripheral *willConnectPeripheral; // 將要連接的設(shè)備脑漫,作為臨時(shí)賦值保存,否則會(huì)出現(xiàn)無法連接的情況
+ (BluetoothManager *)sharedModel;
/// names傳入@[@""]則不過濾設(shè)備名咙崎,scanSystem是否掃描系統(tǒng)當(dāng)前已綁定設(shè)備
- (void)scanWithPrefixNames:(NSArray *)names timeout:(NSTimeInterval)timeout scanBlock:(ScanBlock)scanBlock timeoutBlock:(TimeoutBlock)timeoutBlock;
- (void)stopScan;
- (void)connect:(CBPeripheral *)peripheral timeout:(NSTimeInterval)timeout;
- (void)disconnect:(CBPeripheral *)peripheral;
- (BOOL)getWriteState;
- (void)readValueFromePeripheral;
- (void)writeValueToPeripheral:(NSData *)value block:(WriteBlock)block;
@end
其中BLEUUIDServicesSession优幸、BLEUUIDCharacteristicRead、BLEUUIDCharacteristicWrite褪猛、BLEUUIDCharacteristicNotify是需要你根據(jù)實(shí)際項(xiàng)目的需要進(jìn)行替換的藍(lán)牙UUID网杆。
BluetoothManager.m文件實(shí)現(xiàn)方法:
#import "BluetoothManager.h"
@interface BluetoothManager () <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (copy, nonatomic) ScanBlock scanBlock;
@property (copy, nonatomic) WriteBlock writeBlock;
@property (copy, nonatomic) TimeoutBlock timeoutBlock;
@property (copy, nonatomic) NSArray *scanNames;
@property (strong, nonatomic) NSTimer *threadTimer; // 獲取系統(tǒng)當(dāng)前藍(lán)牙設(shè)備列表線程
@property (strong, nonatomic) CBCharacteristic *characteristicRead;
@property (strong, nonatomic) CBCharacteristic *characteristicWrite;
@property (strong, nonatomic) CBCharacteristic *characteristicNotify;
@property (assign, nonatomic) BOOL isSendingData; // 是否正在發(fā)送數(shù)據(jù)
@end
@implementation BluetoothManager
+ (BluetoothManager *)sharedModel {
static BluetoothManager *sharedInstance;
@synchronized(self) {
if (!sharedInstance) {
sharedInstance = [[BluetoothManager alloc] init];
}
}
return sharedInstance;
}
- (id)init {
if (self = [super init]) {
self.central = [[CBCentralManager alloc] initWithDelegate:self
queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
options:@{CBCentralManagerOptionShowPowerAlertKey : @(NO)}];
}
return self;
}
- (void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark 藍(lán)牙掃描和連接
/////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)scanWithPrefixNames:(NSArray *)names timeout:(NSTimeInterval)timeout scanBlock:(ScanBlock)scanBlock timeoutBlock:(TimeoutBlock)timeoutBlock {
if (names) self.scanNames = names;
if (scanBlock) self.scanBlock = scanBlock;
if (timeoutBlock) self.timeoutBlock = timeoutBlock;
[self stopScan];
self.central.delegate = self;
if (self.central.state == CBCentralManagerStatePoweredOn) {
DLog(@"BluetoothManager: 開始掃描外部設(shè)備!");
[self.central scanForPeripheralsWithServices:nil
options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @(NO)}]; // 是否允許重復(fù)掃描同一設(shè)備
if (timeout > 0) {
[self performSelector:@selector(timeoutScan) withObject:nil afterDelay:timeout];
}
}
}
- (void)stopScan {
[self.threadTimer invalidate];
[self.central stopScan];
DLog(@"BluetoothManager: 停止掃描!");
}
- (void)timeoutScan {
[self stopScan];
if (self.timeoutBlock) {
dispatch_async( dispatch_get_main_queue(), ^{
self.timeoutBlock();
});
}
}
- (void)connect:(CBPeripheral *)peripheral timeout:(NSTimeInterval)timeout {
if (self.peripheral != nil) {
[self disconnect:self.peripheral]; // 斷開當(dāng)前設(shè)備
}
if (peripheral.state != CBPeripheralStateConnected) { // 連接新設(shè)備
[self.central connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey : @(YES),
CBConnectPeripheralOptionNotifyOnDisconnectionKey : @(YES)}];
if (timeout > 0) { // 超時(shí)檢測
[self performSelector:@selector(timeoutConnectPeripheral) withObject:nil afterDelay:timeout];
}
}
}
- (void)disconnect:(CBPeripheral *)peripheral {
if (peripheral.state == CBPeripheralStateConnected) {
[self.central cancelPeripheralConnection:peripheral];
}
}
- (void)timeoutConnectPeripheral {
if (self.peripheral.state != CBPeripheralStateConnected && self.peripheral.state != CBPeripheralStateConnecting) {
DLog(@"BluetoothManager: 連接超時(shí) = %@", self.peripheral.name);
dispatch_async( dispatch_get_main_queue(), ^{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralConnectFailed
object:self.peripheral];
});
[self initAllVariables];
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark CBCentralManagerDelegate
/////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
switch (central.state) {
case CBCentralManagerStatePoweredOn: {
DLog(@"BluetoothManager: 手機(jī)藍(lán)牙打開!");
}
break;
case CBCentralManagerStatePoweredOff: {
DLog(@"BluetoothManager: 手機(jī)藍(lán)牙關(guān)閉!");
[self stopScan];
}
break;
default:
break;
}
dispatch_async( dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationBLECenterChangeState
object:central];
});
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{
if ((-1000 <= RSSI.intValue && RSSI.intValue <= 0)
&& [advertisementData objectForKey:@"kCBAdvDataLocalName"] != nil
// && [advertisementData objectForKey:@"kCBAdvDataServiceUUIDs"] != nil
&& peripheral.name != nil)
{
NSString *deviceName = advertisementData[@"kCBAdvDataLocalName"] ? advertisementData[@"kCBAdvDataLocalName"] : peripheral.name;
DLog(@"BluetoothManager: RSSI = %d, name = %@, advertisementData = %@", [RSSI intValue], deviceName, advertisementData);
for (NSString *scanName in self.scanNames) {
if ([scanName isEqualToString:@""] // 搜索所有名字
|| [deviceName hasPrefix:scanName] // 搜索開頭包含scanName的名字
|| [deviceName rangeOfString:scanName].location != NSNotFound // 搜索字符串包含scanName的名字
)
{
if (self.scanBlock) {
dispatch_async( dispatch_get_main_queue(), ^{
self.scanBlock(peripheral, advertisementData, RSSI);
});
}
}
}
}
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
DLog(@"BluetoothManager: 已連接設(shè)備 = %@", peripheral.name);
[self initAllVariables];
self.peripheral = peripheral;
self.peripheral.delegate = self;
[self.peripheral discoverServices:nil];
[self performSelector:@selector(timeoutDiscoverCharacteristics) withObject:nil afterDelay:5.0];
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
DLog(@"BluetoothManager: 連接失敗 = %@", peripheral.name);
[self initAllVariables];
dispatch_async( dispatch_get_main_queue(), ^{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralConnectFailed
object:peripheral];
});
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
DLog(@"BluetoothManager: 斷開設(shè)備 = %@", peripheral.name);
[self initAllVariables];
dispatch_async( dispatch_get_main_queue(), ^{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralDisconnected
object:peripheral];
});
}
- (void)timeoutDiscoverCharacteristics {
if (self.characteristicRead == nil
|| self.characteristicWrite == nil
|| self.characteristicNotify == nil) {
DLog(@"BluetoothManager: 查找設(shè)備特征值異常碳却!");
dispatch_async( dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralSessionError
object:nil];
});
}
}
- (void)initAllVariables {
self.isSendingData = NO;
self.characteristicRead = nil;
self.characteristicWrite = nil;
self.characteristicNotify = nil;
self.peripheral = nil;
self.peripheral.delegate = nil;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark CBPeripheralDelegate
/////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
if ([error code] != 0) {
DLog(@"BluetoothManager: 藍(lán)牙服務(wù)掃描異常 error = %@", error);
return;
}
DLog(@"BluetoothManager: 藍(lán)牙服務(wù)掃描 = %@", peripheral.services);
for (CBService *services in peripheral.services) {
if ([[services UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDServicesSession]]) {
[peripheral discoverCharacteristics:nil forService:services];
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if ([error code] != 0) {
DLog(@"BluetoothManager: 藍(lán)牙特征值掃描 error = %@", error);
return;
}
DLog(@"BluetoothManager: 藍(lán)牙特征值掃描 %@ = %@", [service UUID], [service characteristics]);
for (CBCharacteristic *characteristic in [service characteristics]) {
// Read
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicRead]]) {
DLog(@"BluetoothManager: 發(fā)現(xiàn) Read 特征值队秩!");
self.characteristicRead = characteristic;
[peripheral readValueForCharacteristic:characteristic];
}
// Write
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicWrite]]) {
DLog(@"BluetoothManager: 發(fā)現(xiàn) Write 特征值!");
self.characteristicWrite = characteristic;
}
// Notify
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicNotify]]) {
DLog(@"BluetoothManager: 發(fā)現(xiàn) Notify 特征值追城!");
self.characteristicNotify = characteristic;
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
// 發(fā)現(xiàn)全部特征值后刹碾,才表示完全連上設(shè)備
if (self.characteristicRead != nil && self.characteristicWrite != nil && self.characteristicNotify != nil) {
dispatch_async( dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralDidConnected
object:peripheral];
});
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if ([error code] != 0) {
DLog(@"BluetoothManager: 數(shù)據(jù)更新 error = %@", error);
dispatch_async( dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralSessionError
object:error];
});
return;
}
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicRead]]
|| [[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicNotify]])
{
dispatch_async( dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralReceiveValue
object:characteristic.value];
});
}
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if ([error code] != 0) {
DLog(@"BluetoothManager: 寫值更新 error = %@", error);
dispatch_async( dispatch_get_main_queue(), ^{
if (self.writeBlock) {
self.writeBlock(NO);
}
self.isSendingData = NO;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralSessionError
object:error];
});
return;
}
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicWrite]]) {
dispatch_async( dispatch_get_main_queue(), ^{
if (self.writeBlock) {
self.writeBlock(YES);
}
self.isSendingData = NO;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralDidSendValue
object:characteristic.value];
});
}
}
- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(nullable NSError *)error {
dispatch_async( dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralReadRSSIValue
object:RSSI];
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark APP To Peripheral
/////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)getWriteState {
if (self.peripheral.state == CBPeripheralStateConnected && self.characteristicWrite != nil) {
return YES;
}
return NO;
}
- (void)readValueFromePeripheral {
if (self.characteristicRead != nil) {
[self.peripheral readValueForCharacteristic:self.characteristicRead];
}
}
- (void)writeValueToPeripheral:(NSData *)value block:(WriteBlock)block {
if (self.peripheral.state != CBPeripheralStateConnected) {
[[NSNotificationCenter defaultCenter] postNotificationName:BLENotificationPeripheralDisconnected
object:nil];
return;
}
if (self.isSendingData) {
DLog(@"BluetoothSession: ###### 正在發(fā)送數(shù)據(jù)! ######");
return;
}
if (self.characteristicWrite != nil) {
if (block) self.writeBlock = block;
self.isSendingData = YES;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(checkSessionTimeout) object:nil];
[self.peripheral writeValue:value forCharacteristic:self.characteristicWrite type:CBCharacteristicWriteWithResponse];
[self performSelector:@selector(checkSessionTimeout) withObject:nil afterDelay:5.0];
DLog(@"BluetoothManager: 寫入數(shù)據(jù) = %@", value);
}
}
- (void)checkSessionTimeout {
if (self.isSendingData == YES) {
DLog(@"BluetoothSession: ###### 藍(lán)牙通信超時(shí)! ######");
if (self.writeBlock) {
self.writeBlock(NO);
}
self.isSendingData = NO;
}
}
@end
使用方法:
1燥撞、在AppDelegate的didFinishLaunchingWithOptions中調(diào)用[BluetoothManager sharedModel]進(jìn)行初始化座柱,如果藍(lán)牙未授權(quán),則此時(shí)系統(tǒng)會(huì)自動(dòng)調(diào)用授權(quán)彈窗物舒。
2色洞、在viewDidLoad中添加藍(lán)牙事件通知監(jiān)聽,在viewDidDisappear中移除通知監(jiān)聽冠胯。
- (void)addBluetoothNotification {
// 藍(lán)牙連接事件
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(BLENotificationBLECenterChangeState:)
name:BLENotificationBLECenterChangeState
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(BLENotificationPeripheralDidConnected:)
name:BLENotificationPeripheralDidConnected
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(BLENotificationPeripheralDisconnected:)
name:BLENotificationPeripheralDisconnected
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(BLENotificationPeripheralConnectFailed:)
name:BLENotificationPeripheralConnectFailed
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(BLENotificationPeripheralSessionError:)
name:BLENotificationPeripheralSessionError
object:nil];
}
- (void)removeBluetoothNotification {
[[NSNotificationCenter defaultCenter] removeObserver:self name:BLENotificationBLECenterChangeState object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:BLENotificationPeripheralDidConnected object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:BLENotificationPeripheralDisconnected object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:BLENotificationPeripheralSessionError object:nil];
}
3火诸、實(shí)現(xiàn)通知監(jiān)聽的回調(diào)方法,來處理藍(lán)牙設(shè)備的不同情況荠察。
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark BluetoothNotification
/////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)BLENotificationBLECenterChangeState:(NSNotification *)notif {
CBCentralManager *central = notif.object;
if (central.state == CBCentralManagerStatePoweredOn) {
// 藍(lán)牙打開
}
else {
// 藍(lán)牙關(guān)閉
}
}
- (void)BLENotificationPeripheralDidConnected:(NSNotification *)notif {
// 設(shè)備連接成功
}
- (void)BLENotificationPeripheralDisconnected:(NSNotification *)notif {
// 設(shè)備斷開連接
}
- (void)BLENotificationPeripheralConnectFailed:(NSNotification *)notif {
// 設(shè)備連接失敗
}
- (void)BLENotificationPeripheralSessionError:(NSNotification *)notif {
// 藍(lán)牙異常狀態(tài)處理
}
4置蜀、調(diào)用scanWithPrefixNames進(jìn)行掃描設(shè)備,如傳入@[@"FOT"]表示只搜索設(shè)備名為FOT開頭的藍(lán)牙設(shè)備悉盆,如果不希望過濾設(shè)備名盯荤,可傳入@[@""];timeout可以指定掃描時(shí)間焕盟,一般為10秒秋秤,10秒過后自動(dòng)停止掃描,也可以傳0脚翘,這時(shí)候會(huì)在后臺(tái)一直掃描灼卢,除非調(diào)用stopScan方法停止掃描。scanBlock為符合條件的設(shè)備回調(diào)来农,你可以在這里對設(shè)備進(jìn)行判斷和連接操作鞋真。timeoutBlock為超時(shí)后的回調(diào),如果不使用可傳nil沃于。
// 掃描設(shè)備名為ABC123的藍(lán)牙設(shè)備涩咖,如果掃描到則進(jìn)行連接
[[BluetoothManager sharedModel] scanWithPrefixNames:@[@"ABC123"] timeout:0.0 scanBlock:^(CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
[BluetoothManager sharedModel].willConnectPeripheral = peripheral; // 必須保存此變量,才可正常連接
[[BluetoothManager sharedModel] connect:[BluetoothManager sharedModel].willConnectPeripheral timeout:10.0]; //連接指定設(shè)備
[[BluetoothManager sharedModel] stopScan]; // 停止掃描
} timeoutBlock:nil];
5揽涮、使用writeValueToPeripheral來將值寫入設(shè)備中抠藕。
[[BluetoothManager sharedModel] writeValueToPeripheral:data block:^(BOOL success) {
if (success) {
// 數(shù)據(jù)寫入成功
}
else {
// 數(shù)據(jù)寫入失敗
}
}];