CBCentralManager、CBPeripheral等简要理解DEMO

本文通过一个简单的DEMO,介绍了CoreBluetooth框架在iOS中的应用,讲解了CBCentralManager和CBPeripheral的核心概念及使用场景。在开始蓝牙开发前,需在Info.plist文件中配置相关背景模式选项,确保App在后台可以进行蓝牙通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先要在Info.plist中添加一个项目

key为Required background modes

类型为array

添加两个值App shares data using CoreBluetooth和App communicates using CoreBluetooth

Central:



//
//  CentralViewController.m
//  CentralPeripheralTool
//
//  Created by LiuMingchuan on 2016/11/29.
//  Copyright © 2016年 LiuMingchuan. All rights reserved.
//

#import "CentralViewController.h"
#import "SinglePeripheralViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>

@interface CentralViewController ()<UITableViewDelegate,UITableViewDataSource,CBCentralManagerDelegate,CBPeripheralDelegate>{
    NSMutableArray *peripherals;
    
}

@property (strong, nonatomic) IBOutlet UITableView *peripheralsTV;
@property (strong, nonatomic) IBOutlet UITextView *logTxtV;
@property (strong, nonatomic)  CBCentralManager *centralManager;

@end

@implementation CentralViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setTitle:@"Central"];
    
    _peripheralsTV.delegate = self;
    _peripheralsTV.dataSource = self;
    
    peripherals = [NSMutableArray array];
    
    //调整因版本问题出现的导航遮挡视图
    if ([[UIDevice currentDevice]systemVersion].doubleValue>7.0) {
        [self setEdgesForExtendedLayout:UIRectEdgeNone];
        [self.navigationController.navigationBar setTranslucent:NO];
    }
    
    _centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil options:@{CBCentralManagerOptionRestoreIdentifierKey:@"Ryoma"}];
    _centralManager.delegate = self;
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"ClearLog" style:UIBarButtonItemStylePlain target:self action:@selector(clearLog)];
    
}

#pragma mark - 清空记录
- (void)clearLog {
    _logTxtV.text = @"";
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [peripherals count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *identifer = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];
    }
    cell.textLabel.text = ((CBPeripheral *)[peripherals objectAtIndex:indexPath.row]).name;
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    CBPeripheral *peripheral = [peripherals objectAtIndex:indexPath.row];
    switch (peripheral.state) {
        case CBPeripheralStateConnected:
        {
            [self showLogMsg:[NSString stringWithFormat:@"%@已经连接",peripheral.name]];
            SinglePeripheralViewController *singlePeripheralVC = [[SinglePeripheralViewController alloc]initWithPeripheral:peripheral];
            [self.navigationController pushViewController:singlePeripheralVC animated:YES];
            break;
        }
        case CBPeripheralStateDisconnected:
        {
            [_centralManager connectPeripheral:peripheral options:nil];
            [self showLogMsg:[NSString stringWithFormat:@"%@正在连接。。。。。。",peripheral.name]];
            break;
        }
        default:
            break;
    }
}

#pragma mark - 是否连接了周边设备
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    [self showLogMsg:[NSString stringWithFormat:@"%@连接成功",peripheral.name]];
    SinglePeripheralViewController *singlePeripheralVC = [[SinglePeripheralViewController alloc]initWithPeripheral:peripheral];
    [self.navigationController pushViewController:singlePeripheralVC animated:YES];
}

#pragma mark - 是否断开了周边设备
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    [self showLogMsg:[NSString stringWithFormat:@"%@断开连接",peripheral.name]];
    [peripherals removeObject:peripheral];
    [_peripheralsTV reloadData];
}

#pragma mark - 连接周边设备失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    [self showLogMsg:[NSString stringWithFormat:@"%@连接失败",peripheral.name]];
    [peripherals removeObject:peripheral];
    [_peripheralsTV reloadData];
}

#pragma mark - 中心设备状态监测
-(void)centralManagerDidUpdateState:(CBCentralManager *)central {
    switch (central.state) {
        case CBManagerStatePoweredOn:
            [_centralManager scanForPeripheralsWithServices:nil options:nil];
            break;
            
        case CBManagerStatePoweredOff:
            [self showLogMsg:@"蓝牙未打开"];
            break;
            
        default:
            [self showLogMsg:@"设备不支持BLE"];
            break;
    }
}

#pragma mark - 检测到周边设备
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    if (![peripherals containsObject:peripheral]) {
        [peripherals addObject:peripheral];
        [self showLogMsg:[NSString stringWithFormat:@"检索到周边%@",[peripheral name]]];
        [_peripheralsTV reloadData];
    }
}

#pragma mark - 状态保存和恢复
-(void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *,id> *)dict {
    //central提供信息,dict包含了应用程序关闭是系统保存的central的信息,用dic去恢复central
    //app状态的保存或者恢复,这是第一个被调用的方法当APP进入后台去完成一些蓝牙有关的工作设置,使用这个方法同步app状态通过蓝牙系统
}

#pragma mark - Log信息显示
- (void)showLogMsg:(NSString *)logMsg {
    if ([@"" isEqualToString:_logTxtV.text]) {
        _logTxtV.text = [NSString stringWithFormat:@">>%@",logMsg];
    } else {
        _logTxtV.text = [NSString stringWithFormat:@"%@\n>>%@",_logTxtV.text,logMsg];
    }
    [_logTxtV scrollRangeToVisible:NSMakeRange(_logTxtV.text.length, 1)];
    _logTxtV.layoutManager.allowsNonContiguousLayout = NO;
}

#pragma mark - other
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end


//
//  SinglePeripheralViewController.m
//  CentralPeripheralTool
//
//  Created by LiuMingchuan on 2016/11/29.
//  Copyright © 2016年 LiuMingchuan. All rights reserved.
//

#import "SinglePeripheralViewController.h"
#import "SingleCharacteristicViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>

@interface SinglePeripheralViewController ()<UITableViewDelegate,UITableViewDataSource,CBPeripheralDelegate> {
    NSMutableArray *services;
}
@property (strong, nonatomic) IBOutlet UITableView *singlePeripheralTV;
@property (strong, nonatomic) IBOutlet UITextView *logTxtV;
@property (strong, nonatomic) CBPeripheral *singlePeripheral;

@end

@implementation SinglePeripheralViewController

-(instancetype)initWithPeripheral:(CBPeripheral *)peripheral {
    self = [super init];
    _singlePeripheral = peripheral;
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setTitle:_singlePeripheral.name];
    
    services = [NSMutableArray array];
    
    _singlePeripheral.delegate = self;
    [_singlePeripheral discoverServices:nil];
    
    _singlePeripheralTV.delegate = self;
    _singlePeripheralTV.dataSource = self;
    
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    CBService *service = (CBService *)[services objectAtIndex:section];
    [self showLogMsg:[NSString stringWithFormat:@"%ld",[service.characteristics count]]];
    return [service.characteristics count];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [services count];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return ((CBService *)[services objectAtIndex:section]).UUID.UUIDString;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    NSString *identifer = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];
    }
    CBService *service = (CBService *)[services objectAtIndex:indexPath.section];
    CBCharacteristic *characteristic = (CBCharacteristic *)[service.characteristics objectAtIndex:indexPath.row];
    cell.textLabel.text = characteristic.UUID.UUIDString;
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    CBService *service = (CBService *)[services objectAtIndex:indexPath.section];
    CBCharacteristic *characteristic = (CBCharacteristic *)[service.characteristics objectAtIndex:indexPath.row];
    SingleCharacteristicViewController *singleCharacteristicVC = [[SingleCharacteristicViewController alloc]initWithPeripheral:_singlePeripheral characteristic:characteristic];
    [self.navigationController pushViewController:singleCharacteristicVC animated:YES];
}

#pragma mark - 搜索周边设备
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    for (CBService *service in peripheral.services) {
        if ([services count]==0) {
            [services addObject:service];
            [peripheral discoverCharacteristics:nil forService:service];
        } else {
            if (![self containsService:service]) {
                [services addObject:service];
                [peripheral discoverCharacteristics:nil forService:service];
            }
        }
    }
}

#pragma mark - 是否已经保存服务
- (Boolean)containsService:(CBService *)service {
    for (CBService *single in services) {
        if ([single.UUID.UUIDString isEqualToString:service.UUID.UUIDString]) {
            return YES;
        }
    }
    return NO;
}

#pragma mark - 有新的特征,更新列表
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    [_singlePeripheralTV reloadData];
}

-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    
}

#pragma mark - Log信息显示
- (void)showLogMsg:(NSString *)logMsg {
    if ([@"" isEqualToString:_logTxtV.text]) {
        _logTxtV.text = [NSString stringWithFormat:@">>%@",logMsg];
    } else {
        _logTxtV.text = [NSString stringWithFormat:@"%@\n>>%@",_logTxtV.text,logMsg];
    }
    [_logTxtV scrollRangeToVisible:NSMakeRange(_logTxtV.text.length, 1)];
    _logTxtV.layoutManager.allowsNonContiguousLayout = NO;
}

#pragma mark - other
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

//
//  SingleCharacteristicViewController.m
//  CentralPeripheralTool
//
//  Created by LiuMingchuan on 2016/11/30.
//  Copyright © 2016年 LiuMingchuan. All rights reserved.
//

#import "SingleCharacteristicViewController.h"

@interface SingleCharacteristicViewController ()<CBPeripheralDelegate>

@property (strong, nonatomic) CBPeripheral *singlePeripheral;
@property (strong, nonatomic) CBCharacteristic *singleCharacteristic;
@property (weak, nonatomic) IBOutlet UITextView *logTxtV;

@end

@implementation SingleCharacteristicViewController

-(instancetype)initWithPeripheral:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic {
    self = [super init];
    _singlePeripheral = peripheral;
    _singleCharacteristic = characteristic;
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setTitle:_singleCharacteristic.UUID.UUIDString];
    
    _singlePeripheral.delegate = self;
    [_singlePeripheral setNotifyValue:YES forCharacteristic:_singleCharacteristic];
    // Do any additional setup after loading the view from its nib.
}

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (characteristic.value) {
        NSString *value = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        [self showLogMsg:value];
    } else {
        [self showLogMsg:@"没有特征值"];
    }
}

#pragma mark - Log信息显示
- (void)showLogMsg:(NSString *)logMsg {
    if ([@"" isEqualToString:_logTxtV.text]) {
        _logTxtV.text = [NSString stringWithFormat:@">>%@",logMsg];
    } else {
        _logTxtV.text = [NSString stringWithFormat:@"%@\n>>%@",_logTxtV.text,logMsg];
    }
    [_logTxtV scrollRangeToVisible:NSMakeRange(_logTxtV.text.length, 1)];
    _logTxtV.layoutManager.allowsNonContiguousLayout = NO;
}

#pragma mark - other
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

Peripheral:


//
//  PeripheralViewController.m
//  BluetoothDemo
//
//  Created by LiuMingchuan on 2016/11/26.
//  Copyright © 2016年 LiuMingchuan. All rights reserved.
//

#import "PeripheralViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>

@interface PeripheralViewController ()<CBPeripheralManagerDelegate>

@property (strong,nonatomic)CBPeripheralManager *peripheralManager;
@property (strong,nonatomic)NSMutableArray *centralList;
@property (strong,nonatomic)CBMutableService *service;
@property (strong,nonatomic)CBMutableCharacteristic *characteristic;
@property (strong,nonatomic) NSTimer *timer;

@property (weak, nonatomic) IBOutlet UIButton *startPeripheralBtn;
@property (weak, nonatomic) IBOutlet UIButton *updateCharacteristicBtn;
@property (weak, nonatomic) IBOutlet UITextView *logTxtV;

@end

@implementation PeripheralViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setTitle:@"Peripheral"];

    if([[[UIDevice currentDevice]systemVersion]floatValue]>7.0){
        [self.navigationController.navigationBar setTranslucent:NO];
        self.edgesForExtendedLayout = UIRectEdgeNone;
    }
    
    [_startPeripheralBtn addTarget:self action:@selector(startPeripheralBtnAction:) forControlEvents:UIControlEventTouchUpInside];
    [_updateCharacteristicBtn addTarget:self action:@selector(updateCharacteristicBtnAction:) forControlEvents:UIControlEventTouchUpInside];
    
    // Do any additional setup after loading the view from its nib.
}
#pragma mark - 打开外围设备
- (void)startPeripheralBtnAction:(id)sender {
    if (!_peripheralManager) {
        _peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
        _centralList = [NSMutableArray array];
    }
}

#pragma mark - 使用计时器开始更新特征值
- (void)updateCharacteristicBtnAction:(id)sender {
    if (!_timer) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(autoMakeRate) userInfo:nil repeats:YES];
    } else {
        [_timer invalidate];
        _timer = nil;
    }
}

- (void)autoMakeRate {
    if (_peripheralManager) {
        NSString *value = [NSString stringWithFormat:@"%@:%d",[NSDate date],arc4random()%30+60];
        NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
        [_peripheralManager updateValue:data forCharacteristic:_characteristic onSubscribedCentrals:nil];
        
        [self showLogMsg:[NSString stringWithFormat:@"新值 %@",value]];
    } else {
        [self showLogMsg:@"未打开周边模式"];
        [_timer invalidate];
        _timer = nil;
    }
}

-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    switch (peripheral.state) {
        case CBManagerStatePoweredOn:
            //
            [self setUpService];
            break;
        case CBManagerStatePoweredOff:
            [self showLogMsg:@"蓝牙未打开"];
            break;
            
        default:
            //不支持BLE
            [self showLogMsg:@"设备不支持BLE"];
            _peripheralManager = nil;
            break;
    }
}

- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
    
    [self showLogMsg:[NSString stringWithFormat:@"中心:%@ 订阅特征:%@。",central.identifier.UUIDString,characteristic.UUID]];
    if (![_centralList containsObject:central]) {
        [_centralList addObject:central];
    }
}

-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
    [self showLogMsg:[NSString stringWithFormat:@"中心:%@ 取消订阅特征:%@。",central.identifier.UUIDString,characteristic.UUID]];
}

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
    [self showLogMsg:@"Central Read Data From Peripheral"];
    [self showLogMsg:[[NSString alloc]initWithData:request.value encoding:NSUTF8StringEncoding]];
}

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
    [self showLogMsg:@"Receive Data From Peripheral"];
    CBATTRequest *request = requests.lastObject;
    [self showLogMsg:[[NSString alloc]initWithData:request.value encoding:NSUTF8StringEncoding]];
    [peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}

-(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {
    NSDictionary *dic = @{CBAdvertisementDataLocalNameKey:@"Ryoma's Peripheral"};
    [_peripheralManager startAdvertising:dic];
    
    [self showLogMsg:@"想周边添加了服务"];
}

-(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {
    [self showLogMsg:@"开始广播。。。。。。"];
}


#pragma mark - Log信息显示
- (void)showLogMsg:(NSString *)logMsg {
    if ([@"" isEqualToString:_logTxtV.text]) {
        _logTxtV.text = [NSString stringWithFormat:@">>%@",logMsg];
    } else {
        _logTxtV.text = [NSString stringWithFormat:@"%@\n>>%@",_logTxtV.text,logMsg];
    }
    [_logTxtV scrollRangeToVisible:NSMakeRange(_logTxtV.text.length, 1)];
    _logTxtV.layoutManager.allowsNonContiguousLayout = NO;
}

#pragma mark - 服务设定
- (void)setUpService {
    CBUUID *characteristicUUID = [CBUUID UUIDWithString:@"18035BA8-C04C-4CBF-93F4-FB2BB5ACFC46"];
    _characteristic = [[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyWrite|CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsWriteEncryptionRequired];
    
    CBUUID *serviceUUID = [CBUUID UUIDWithString:@"C03D84A9-F731-4B38-9486-DCA7387A9753"];
    _service = [[CBMutableService alloc]initWithType:serviceUUID primary:YES];
    [_service setCharacteristics:@[_characteristic]];
    
    [_peripheralManager addService:_service];
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(void)dealloc {
    [_timer invalidate];
}
/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值