程序开发

iOS开发丨蓝牙4.0复制粘贴就能使用的CoreBluetoot

2019-12-30  本文已影响0人  炼心术师

作为多年的iOS蓝牙开发者,下面共享一下我自己搭建和使用的蓝牙框架。首先创建一个类BluetoothManager,用来管理CBCentralManager和CBPeripheral,以及一些扫描连接的公共方法,其中数据和状态回调使用通知的形式进行处理,这样在整个项目中,只需要保存一份BluetoothManager就可以了,而不是在每个需要蓝牙的页面都生成CBCentralManager。

BluetoothManager.h文件实现:

#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// 通知回调,需要接受消息的类必须注册对应的通知

static NSString * const BLENotificationBLECenterChangeState    = @"BLENotificationBLECenterChangeState";     // 蓝牙状态改变
static NSString * const BLENotificationPeripheralDidConnected  = @"BLENotificationPeripheralDidConnected";   // 设备连接成功
static NSString * const BLENotificationPeripheralConnectFailed = @"BLENotificationPeripheralConnectFailed";  // 设备连接失败
static NSString * const BLENotificationPeripheralDisconnected  = @"BLENotificationPeripheralDisconnected";   // 设备断开连接
static NSString * const BLENotificationPeripheralDidSendValue  = @"BLENotificationPeripheralDidSendValue";   // 写入设备数据
static NSString * const BLENotificationPeripheralReceiveValue  = @"BLENotificationPeripheralReceiveValue";   // 收到设备数据
static NSString * const BLENotificationPeripheralSessionError  = @"BLENotificationPeripheralSessionError";   // 设备会话异常
static NSString * const BLENotificationPeripheralReadRSSIValue = @"BLENotificationPeripheralReadRSSIValue";  // 设备信号强度

/////////////////////////////////////////////////////////////////////////////////////////////////////////

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;  // 当前已连接上的设备
@property (strong, nonatomic) CBPeripheral *willConnectPeripheral;  // 将要连接的设备,作为临时赋值保存,否则会出现无法连接的情况

+ (BluetoothManager *)sharedModel;
/// names传入@[@""]则不过滤设备名,scanSystem是否扫描系统当前已绑定设备
- (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是需要你根据实际项目的需要进行替换的蓝牙UUID。

BluetoothManager.m文件实现方法:

#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;  // 获取系统当前蓝牙设备列表线程
@property (strong, nonatomic) CBCharacteristic *characteristicRead;
@property (strong, nonatomic) CBCharacteristic *characteristicWrite;
@property (strong, nonatomic) CBCharacteristic *characteristicNotify;
@property (assign, nonatomic) BOOL isSendingData;    // 是否正在发送数据

@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 蓝牙扫描和连接
/////////////////////////////////////////////////////////////////////////////////////////////////////////

- (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: 开始扫描外部设备!");
        [self.central scanForPeripheralsWithServices:nil
                                             options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @(NO)}];  // 是否允许重复扫描同一设备
        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];  // 断开当前设备
    }
    if (peripheral.state != CBPeripheralStateConnected) {  // 连接新设备
        [self.central connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey : @(YES),
                                                             CBConnectPeripheralOptionNotifyOnDisconnectionKey : @(YES)}];
        if (timeout > 0) {  // 超时检测
            [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: 连接超时 = %@", 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: 手机蓝牙打开!");
        }
            break;
        case CBCentralManagerStatePoweredOff: {
            DLog(@"BluetoothManager: 手机蓝牙关闭!");
            [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: 已连接设备 = %@", 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: 断开设备 = %@", 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: 查找设备特征值异常!");
        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: 蓝牙服务扫描异常 error = %@", error);
        return;
    }
    DLog(@"BluetoothManager: 蓝牙服务扫描 = %@", 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: 蓝牙特征值扫描 error = %@", error);
        return;
    }
    DLog(@"BluetoothManager: 蓝牙特征值扫描 %@ = %@", [service UUID], [service characteristics]);
    
    for (CBCharacteristic *characteristic in [service characteristics]) {
        // Read
        if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicRead]]) {
            DLog(@"BluetoothManager: 发现 Read 特征值!");
            self.characteristicRead = characteristic;
            [peripheral readValueForCharacteristic:characteristic];
        }
        // Write
        if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicWrite]]) {
            DLog(@"BluetoothManager: 发现 Write 特征值!");
            self.characteristicWrite = characteristic;
        }
        // Notify
        if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:BLEUUIDCharacteristicNotify]]) {
            DLog(@"BluetoothManager: 发现 Notify 特征值!");
            self.characteristicNotify = characteristic;
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }
    }
    
    // 发现全部特征值后,才表示完全连上设备
    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: 数据更新 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: ###### 正在发送数据! ######");
        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: 写入数据 = %@", value);
    }
}

- (void)checkSessionTimeout {
    if (self.isSendingData == YES) {
        DLog(@"BluetoothSession: ###### 蓝牙通信超时! ######");
        if (self.writeBlock) {
            self.writeBlock(NO);
        }
        self.isSendingData = NO;
    }
}

@end

使用方法:

1、在AppDelegate的didFinishLaunchingWithOptions中调用[BluetoothManager sharedModel]进行初始化,如果蓝牙未授权,则此时系统会自动调用授权弹窗。

2、在viewDidLoad中添加蓝牙事件通知监听,在viewDidDisappear中移除通知监听。

- (void)addBluetoothNotification {
    // 蓝牙连接事件
    [[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、实现通知监听的回调方法,来处理蓝牙设备的不同情况。

/////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark BluetoothNotification
/////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)BLENotificationBLECenterChangeState:(NSNotification *)notif {
    CBCentralManager *central = notif.object;
    if (central.state == CBCentralManagerStatePoweredOn) {
        // 蓝牙打开
    }
    else {
        // 蓝牙关闭
    }
}

- (void)BLENotificationPeripheralDidConnected:(NSNotification *)notif {
    // 设备连接成功
}

- (void)BLENotificationPeripheralDisconnected:(NSNotification *)notif {
    // 设备断开连接
}

- (void)BLENotificationPeripheralConnectFailed:(NSNotification *)notif {
    // 设备连接失败
}

- (void)BLENotificationPeripheralSessionError:(NSNotification *)notif {
    // 蓝牙异常状态处理
}

4、调用scanWithPrefixNames进行扫描设备,如传入@[@"FOT"]表示只搜索设备名为FOT开头的蓝牙设备,如果不希望过滤设备名,可传入@[@""];timeout可以指定扫描时间,一般为10秒,10秒过后自动停止扫描,也可以传0,这时候会在后台一直扫描,除非调用stopScan方法停止扫描。scanBlock为符合条件的设备回调,你可以在这里对设备进行判断和连接操作。timeoutBlock为超时后的回调,如果不使用可传nil。

// 扫描设备名为ABC123的蓝牙设备,如果扫描到则进行连接
[[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];  //连接指定设备
    [[BluetoothManager sharedModel] stopScan];  // 停止扫描
} timeoutBlock:nil];

5、使用writeValueToPeripheral来将值写入设备中。

[[BluetoothManager sharedModel] writeValueToPeripheral:data block:^(BOOL success) {
    if (success) {
        // 数据写入成功
    }
    else {
        // 数据写入失败
    }
}];
上一篇 下一篇

猜你喜欢

热点阅读