在 iOS 蓝牙开发中,很多人把精力集中在 CBCentralManager 和 CBPeripheralManager 上,但真正决定蓝牙是否"能正常工作"的,其实是它们的父类:CBManager。
如果对 CBManager 理解不透,通常会出现这些问题:
- 扫描时机不对(经常扫不到设备)
- 权限处理混乱
- 蓝牙开关变化后逻辑异常
- SDK 封装设计不合理
这篇文章从设计本质 + 状态模型 + 实战用法 + SDK 封装 四个层面,彻底讲清楚 CBManager。
一、CBManager 的本质
先看继承关系:
CBManager
├── CBCentralManager
└── CBPeripheralManager
👉 结论:
CBManager 是 CoreBluetooth 中所有蓝牙管理器的统一状态抽象层。
它本身不能被实例化:
objectivec
- (instancetype)init NS_UNAVAILABLE;
它只负责两件事:
当前蓝牙系统状态(state)
当前应用权限状态(authorization)
二、CBManagerState:蓝牙"能不能用"的唯一依据
objectivec
typedef NS_ENUM(NSInteger, CBManagerState) {
CBManagerStateUnknown = 0,
CBManagerStateResetting,
CBManagerStateUnsupported,
CBManagerStateUnauthorized,
CBManagerStatePoweredOff,
CBManagerStatePoweredOn,
};
这是整个 CoreBluetooth 的核心状态机。
1. Unknown(未知状态)
- 初始化后默认状态
- 很快会进入其他状态
👉 此时不能做任何蓝牙操作
2. Resetting(系统重置中)
- 蓝牙服务短暂不可用
- CoreBluetooth 正在恢复
👉 实战建议:
暂停业务,等待状态恢复
3. Unsupported(设备不支持)
- 当前设备不支持 BLE
👉 现代 iPhone 基本不会出现,但必须处理。
4. Unauthorized(没有权限)
- App 没有蓝牙权限
👉 可能原因:
- 用户拒绝
- 系统限制(如家长控制)
5. PoweredOff(蓝牙关闭)
- 系统蓝牙开关关闭
👉 和 Unauthorized 区别:
| 状态 | 含义 |
|---|---|
| Unauthorized | 没权限 |
| PoweredOff | 蓝牙没开 |
6. PoweredOn(唯一可用状态)
👉 只有这个状态下,才能执行所有蓝牙操作:
objectivec
scan / connect / discover / write / notify / advertise
三、CBManagerAuthorization:权限模型
objectivec
typedef NS_ENUM(NSInteger, CBManagerAuthorization) {
CBManagerAuthorizationNotDetermined = 0,
CBManagerAuthorizationRestricted,
CBManagerAuthorizationDenied,
CBManagerAuthorizationAllowedAlways
};
状态含义
| 状态 | 含义 |
|---|---|
| NotDetermined | 用户尚未选择 |
| Restricted | 系统限制 |
| Denied | 用户拒绝 |
| AllowedAlways | 已授权 |
⚠️ 关键认知
authorization ≠ 蓝牙是否可用
例如:
AllowedAlways + PoweredOff → 仍然不能用
四、正确使用流程(核心重点)
很多人的错误流程:
先判断权限 → 再创建 manager → 再做业务
❌ 错误。
✅ 正确流程
创建 manager
等待状态回调
state == PoweredOn → 开始业务
标准代码
objectivec
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
switch (central.state) {
case CBManagerStatePoweredOn:
[self startScan];
break;
case CBManagerStatePoweredOff:
// 提示用户打开蓝牙
break;
case CBManagerStateUnauthorized:
// 提示权限问题
break;
case CBManagerStateResetting:
// 暂停业务
break;
default:
break;
}
}
一句话总结
是否能做蓝牙业务,只看:
state == CBManagerStatePoweredOn
五、为什么必须等 PoweredOn?
因为:
state 是"最终裁决者"
它综合了:
- 权限状态
- 蓝牙开关状态
- 系统服务状态
六、authorization 的正确用法
它是辅助信息,而不是流程控制核心。
用法 1:优化用户体验
objectivec
if (@available(iOS 13.1, *)) {
if (CBManager.authorization == CBManagerAuthorizationDenied) {
// 提前提示用户
}
}
用法 2:区分 Unauthorized 原因
objectivec
if (central.state == CBManagerStateUnauthorized) {
if (@available(iOS 13.1, *)) {
if (CBManager.authorization == CBManagerAuthorizationDenied) {
// 用户拒绝
}
}
}
七、SDK 封装设计(实战重点)
如果你在做蓝牙 SDK,这一节非常关键。
❌ 不推荐设计
objectivec
@property (nonatomic, assign, readonly) CBManagerState state;
问题:
- 暴露系统实现细节
- 上层需要理解 CoreBluetooth
- 不利于扩展
✅ 推荐设计
1. 自定义状态
objectivec
typedef NS_ENUM(NSInteger, BluetoothState) {
BluetoothStateUnknown = 0,
BluetoothStateResetting,
BluetoothStateUnsupported,
BluetoothStateUnauthorized,
BluetoothStatePoweredOff,
BluetoothStatePoweredOn,
};
- 提供只读属性
objectivec
@property (nonatomic, assign, readonly) BluetoothState bluetoothState;
- 提供状态回调(必须)
objectivec
@protocol BluetoothManagerDelegate <NSObject>
@optional
- (void)bluetoothManagerDidUpdateState:(BluetoothState)state;
@end
为什么必须要回调?
因为状态是动态变化的:
Unknown → PoweredOn
PoweredOn → PoweredOff
PoweredOff → PoweredOn
如果只有属性:
objectivec
manager.bluetoothState
👉 上层无法感知变化。
标准实现
objectivec
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
BluetoothState newState = [self mapState:central.state];
_bluetoothState = newState;
if ([self.delegate respondsToSelector:@selector(bluetoothManagerDidUpdateState:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate bluetoothManagerDidUpdateState:newState];
});
}
}
八、常见错误总结
❌ 初始化后立即扫描
objectivec
[self.central scanForPeripheralsWithServices:nil options:nil];
👉 错误(state 未 ready)
❌ 只判断权限
objectivec
if (CBManager.authorization == AllowedAlways)
👉 不够,必须看 state
❌ 忽略 Resetting
👉 会导致"偶现问题"
❌ 只暴露属性,没有回调
👉 SDK 设计不完整
九、总结
你只需要记住三点:
1️⃣ CBManager 是蓝牙状态抽象层
统一管理:state + authorization
2️⃣ 唯一业务入口
state == CBManagerStatePoweredOn
3️⃣ SDK 设计原则
属性:当前状态
回调:状态变化