从 iBeacon 到自动解锁:为什么复杂 iOS 能力系统最终都需要 `Protocol + Impl + Router + Coordinator`

很多架构设计,只有在系统变复杂之后,才会显现出它真正的价值。

在业务简单的时候,直接写实现类、直接调单例、直接在回调里串逻辑,往往也能工作;甚至从短期交付视角看,这样做还更"高效"。

但一旦系统开始具备下面这些特征,原本那些"能跑就行"的设计,几乎都会在后续迭代中逐渐失效:

  • 存在多个系统入口
  • 存在长生命周期共享服务
  • 存在异步事件驱动链路
  • 存在大量边缘状态与竞争条件
  • 存在安全兜底与策略演进需求
  • 存在调试、回放、遥测与问题追踪诉求

我最近在做一个近场感知相关的 iOS Demo,场景不算大,但很典型:

  • 使用 CoreLocation 监听 iBeacon 区域
  • 进入区域后触发 BLE 扫描
  • 识别候选车辆
  • 根据授权结果决定是否允许自动连接
  • 成功后发送解锁指令
  • 全流程接入 telemetry,记录状态流转与功耗指标
  • 同时要处理防抖、宽限期、冷却期、多车冲突、权限兜底等问题

这类系统表面上是"定位 + 蓝牙 + 埋点"的组合,实际上它考验的从来不是单点 API 的使用,而是复杂能力链路的治理能力。

在这个过程中,我越来越确信一件事:

对于这类能力型 iOS 系统,Protocol + Impl + Router + Coordinator 不是"设计模式爱好者的自我感动",而是一种把复杂度前移到设计期治理的必要手段。

本文想聊的,不是"这个模式怎么写",而是更根本的问题:

  • 为什么复杂能力系统最终会逼近这种结构
  • 为什么它不只是解耦,而是在治理依赖、状态和运行时风险
  • 为什么 Router 不等于 Service Locator 反模式
  • 为什么 Coordinator 的真正定位应该是"控制面",而不是"大号业务类"

一、复杂系统的真正矛盾,从来不是功能实现,而是复杂度治理

在多数移动端项目里,大家对复杂度的第一反应通常是 UI 层复杂、页面逻辑复杂、接口编排复杂。

但近场感知、蓝牙、后台唤醒这类系统不同。

它们的复杂度不集中在界面,而集中在以下几个维度:

  • 能力跨层协同
  • 生命周期跨组件存在
  • 事件由系统驱动而非用户显式驱动
  • 状态流转具有异步性和偶发性
  • 风险主要出现在边缘场景而非主路径

换句话说,这不是一个"页面架构"问题,而是一个"能力系统架构"问题。

在这类系统里,最容易失控的不是业务分支,而是三个更底层的问题:

  • 依赖关系是否可见
  • 对象生命周期是否一致
  • 状态流转是否确定

很多线上难查的问题,最后追根溯源,其实都落在这三类失控上。

例如:

  • AppDelegate 和页面层拿到的不是同一个服务实例
  • 某个蓝牙服务提前初始化,另一个模块却持有旧引用
  • 某个 delegate 被静默覆盖,导致回调链路失真
  • 多个入口都能直接触发自动连接,导致状态竞争
  • 同一个 beacon 事件因抖动被重复消费多次
  • 功耗异常时没有统一观测点,无法解释为什么扫描一直没停

这些问题本质上都不是"某一行代码写错了",而是系统缺少明确的治理结构。

所以架构设计的意义,并不是把代码"分类整理",而是为复杂度建立秩序。


二、Protocol + Impl + Router + Coordinator 不是分层技巧,而是治理模型

很多人第一次看到这套结构,会把它理解成一种"组件化模板":

  • Protocol 放接口
  • Impl 放实现
  • Router 做注册
  • Coordinator 做编排

如果只停留在这个层面,它的价值会被严重低估。

从更高层抽象看,这四个角色其实分别治理了复杂系统里的四类问题:

  • Protocol 治理能力边界
  • Impl 治理平台细节
  • Router 治理对象装配与生命周期
  • Coordinator 治理跨服务状态编排

也就是说,这不是简单的"分层",而是一种复杂系统的职责分治模型。

1. Protocol 不是接口文件,而是能力边界的声明

在工程里,最危险的耦合从来不是"调用了别人的方法",而是调用方开始感知对方的内部结构。

一旦上层知道了下层的实现细节,它就会逐渐形成一系列隐式假设:

  • 这个服务一定是这样初始化的
  • 这个 delegate 一定是这样工作的
  • 这个回调一定在主线程
  • 这个属性一定已经准备好了
  • 这个类以后也一定还是这个类

这些假设短期看不到问题,长期一定会演化成演进阻力。

Protocol 的真正意义,是让上层依赖"能力语义",而不是"实现形状"。

比如在近场感知场景中,上层真正需要知道的是:

  • 我能否监听区域进入与离开
  • 我能否发起扫描与连接
  • 我能否判断某个目标是否授权
  • 我能否记录某种状态变化与指标

而不是:

  • 蓝牙内部是怎么维护 peripheral 列表的
  • beacon 回调是怎么拼装上下文的
  • telemetry 底层是写 NSUserDefaults 还是上传远端

这不是抽象洁癖,而是避免实现细节上溢的必要手段。

2. Impl 不是"实现类目录",而是平台复杂度的隔离层

能力型系统的底层实现,天然需要吸收大量平台细节:

  • CoreLocation 的授权与回调模型
  • CoreBluetooth 的扫描、连接、发现服务、GATT 通道状态
  • 后台模式约束
  • 权限说明与系统行为差异
  • 本地存储、时间窗口、线程切换等非业务细节

这些内容必须存在,但不应该上浮到业务编排层。

因此,Impl 的本质职责不是"把协议实现掉",而是吸收平台复杂度,让上层看到一个稳定、可推理的能力模型。

如果这一层失守,常见结果就是:

  • 业务层开始感知太多系统行为
  • 编排层被底层回调模型反向塑造
  • 平台约束泄漏到多个模块
  • 后续替换实现几乎不可能

所以从架构角度说,Impl 是复杂系统的"噪音吸收层"。

3. Router 的本质不是找对象,而是收口对象治理权

这一步往往最容易被低估。

很多人做了协议与实现分离之后,觉得系统已经解耦了。但在真实工程里,更常见的失败不是"依赖错了实现",而是"拿错了实例"。

状态型服务尤其如此。

以 iBeaconService、BluetoothService 为例,这类对象通常具有以下特征:

  • 生命周期长
  • 状态持续存在
  • 可能承接系统 delegate
  • 需要跨入口共享
  • 对"是不是同一个对象"高度敏感

如果没有统一的装配入口,系统很快就会演化成:

  • 谁先用谁初始化
  • 谁想拿谁创建
  • AppDelegate 自己保一份
  • 页面层再保一份
  • 某个工具类顺手又生成一份
  • 最后表面上都符合协议,运行时却根本不是同一个世界

这就是为什么我一直强调:Router 的价值不是"查找服务",而是"统一对象治理权"。

它解决的不是语法问题,而是运行时一致性问题。

4. Coordinator 的本质不是中转调度,而是控制面

这是我最想强调的一点。

很多团队引入 Coordinator,最后只是把原来散落在各处的调用集中到了一个更大的类里。文件变大了,但架构并没有真正升级。

一个成熟的 Coordinator,绝不是"什么都往里塞"的中转站,而应该被理解为复杂能力系统的"控制面"。

它真正要治理的是:

  • 事件接入
  • 状态推进
  • 策略裁决
  • 动作派发
  • 风险兜底
  • 观测收口

在近场感知场景里,Coordinator 不只是把 Beacon 和 BLE 串起来,而是在定义整条链路的运行规则:

  • 什么事件可以进入系统
  • 进入后处于什么状态
  • 哪些条件满足才能推进
  • 遇到什么风险必须拒绝
  • 拒绝的原因如何标准化
  • 状态变化如何被记录与追踪

当你把 Coordinator 提升到"控制面"视角时,这套架构才开始真正发挥威力。


三、这套架构为什么特别适合 iOS 能力型系统

不是所有项目都需要这套设计。

如果系统足够简单,页面足够单一,流程足够短,直接写实现类也并不会立刻出问题。

但一旦出现下面这些特征,这套结构的收益会迅速放大:

  • 服务是长生命周期对象
  • 服务承接系统能力或硬件能力
  • 多个入口会访问同一服务
  • 存在异步时序竞争
  • 规则会持续演进
  • 需要 mock、回放、调试与遥测

而近场感知系统恰好同时具备这些特征。

原因在于,它不是一个"功能点",而是一条"异步控制链":

  • 系统感知事件进入
  • 状态机确认是否接受
  • 权限与策略层决定是否放行
  • 底层能力服务执行扫描与连接
  • 结果反馈回控制面
  • 控制面决定是否推进、回退、降级或拒绝
  • 观测层记录全过程

这类链路最怕的,不是多一层抽象,而是没有控制面。


四、架构图:从能力堆叠到治理闭环

下面这张图更适合用来理解这套结构的"治理含义",而不只是"模块结构"。

flowchart LR Entry[AppDelegate / ViewController / Background Wakeup] Router[Router / Service Registry] Coord[Coordinator / Control Plane] FSM[FSM] Policy[Policy Set] Snapshot[Telemetry & Diagnostic Snapshot] BeaconP[IBeaconServiceProtocol] BeaconI[IBeaconServiceImpl] BleP[BluetoothServiceProtocol] BleI[BluetoothServiceImpl] AccessP[VehicleAccessServiceProtocol] AccessI[VehicleAccessServiceImpl] TelemetryP[UnlockTelemetryServiceProtocol] TelemetryI[UnlockTelemetryServiceImpl] CL[CoreLocation] CB[CoreBluetooth] Store[Local Metrics Store / Remote Telemetry] Entry --> Router Router --> Coord Coord --> FSM Coord --> Policy Coord --> Snapshot Coord --> BeaconP Coord --> BleP Coord --> AccessP Coord --> TelemetryP BeaconP -.协议映射.-> BeaconI BleP -.协议映射.-> BleI AccessP -.协议映射.-> AccessI TelemetryP -.协议映射.-> TelemetryI BeaconI --> CL BleI --> CB TelemetryI --> Store BeaconI -- region events --> Coord Coord -- connect / reject / cooldown --> BleI Coord -- access decision --> AccessI Coord -- state / power / reason --> TelemetryI

如果只看技术栈,这不过是定位、蓝牙、埋点和权限服务的组合;但如果从治理视角看,它已经形成了一个完整闭环:

  • Protocol 让能力边界稳定
  • Impl 让平台复杂度下沉
  • Router 让对象治理统一
  • Coordinator 让控制决策集中
  • FSM + Policy + Snapshot 让状态确定、规则可演进、过程可观测

这才是这套架构最深层的价值。


五、时序图:控制面如何统一接管整条能力链

再看一次完整链路,会更容易理解 Coordinator 的地位。

sequenceDiagram participant Entry as AppDelegate / VC participant Router as Router participant Coord as Coordinator participant Beacon as IBeaconService participant Access as VehicleAccessService participant BLE as BluetoothService participant Telemetry as UnlockTelemetryService Entry->>Router: 获取 Coordinator Router-->>Entry: 返回共享实例 Beacon-->>Coord: didEnterRegion Coord->>Telemetry: 记录 flow_state_changed Coord->>Coord: FSM 防抖 / 进入确认 Coord->>Access: 过滤授权车辆 Access-->>Coord: 返回可用候选 alt 唯一合法候选 Coord->>BLE: 开始扫描与连接 BLE-->>Coord: 返回连接结果 Coord->>Telemetry: 记录扫描耗时 / 成功率 / GATT 时长 else 多车或未授权 Coord->>Telemetry: 记录 rejected reason end Beacon-->>Coord: didExitRegion Coord->>Coord: 进入 Grace Period 或 Cooldown Coord->>Telemetry: 记录状态迁移

这里最重要的一点,不是流程本身,而是"权力归属":

  • 触发事件来自系统
  • 执行动作来自底层服务
  • 但链路推进权与拒绝权必须掌握在 Coordinator 手里

这是复杂系统设计里非常关键的一条原则:

能力可以分散,决策必须收口。


六、为什么 Router 不等于 Service Locator 反模式

几乎所有讨论到 Router 的场合,都会遇到一个经典质疑:

"这不就是 Service Locator 吗?"

这个问题很重要,因为它恰好能帮助我们厘清"受控装配"和"隐藏依赖"之间的本质区别。

Service Locator 真正的问题,从来不是中心入口

反模式的关键,并不是存在一个中心对象,而是这个中心对象导致依赖关系变得不可见。

比如某个类初始化时看起来没有依赖,但方法内部却不断地去全局查找:

  • beacon service
  • bluetooth service
  • telemetry service
  • access service

这样做的结果是:

  • 类的真实依赖无法从接口签名中看出
  • 测试必须搭建全局容器环境
  • 运行时行为依赖外部注册状态
  • 系统的静态结构与运行时结构完全脱节

这才是 Service Locator 被批评为反模式的真正原因。

一个健康的 Router,有三个关键约束

如果你的 Router 满足下面三个约束,它就不是反模式,而是工程化装配层:

  • 只在装配边界使用,而不是在运行期随处动态查找
  • 只按协议提供依赖,而不是暴露具体实现
  • 只负责注册、映射、共享实例与生命周期策略,而不承载业务规则

一句话概括:

Service Locator 的问题是"在使用期隐藏依赖",

而健康的 Router 做的是"在装配期显式组织依赖"。

在 Objective-C 和 iOS 的实际工程环境里,完全不依赖中心注册往往并不现实。因为系统入口天然分散,生命周期天然跨组件,静态依赖注入链很难完全覆盖所有场景。

所以真正的关键不是"是否有 Router",而是"Router 是否受控"。


七、随着需求增加,Coordinator 为什么必须从"逻辑汇总"升级为"控制内核"

如果说 Router 最容易退化成 Service Locator,那 Coordinator 最容易退化成另一个问题:God Object。

这几乎是所有复杂编排层最终都会面对的宿命。

因为一旦系统继续生长,新需求很自然就会不断落到 Coordinator:

  • 增加多车优先级策略
  • 增加不同身份下的准入逻辑
  • 增加扫描超时重试
  • 增加连接失败退避
  • 增加前后台差异化策略
  • 增加功耗保护阈值
  • 增加灰度实验开关
  • 增加更多 telemetry 字段

如果没有结构化规划,新增需求通常都会以最简单的形式进入:继续加 if/else、继续加布尔值、继续插回调分支。

最终得到的不是一个协调器,而是一个复杂度黑洞。

一个可持续演进的 Coordinator,必须具备"控制内核"思维

我更倾向于把 Coordinator 拆成如下几个稳定部分:

  • Event Input
  • FSM
  • Policy Set
  • Action Dispatcher
  • Telemetry / Diagnostic Snapshot

也就是说,Coordinator 本体应该只负责五件事:

  • 接收外部事件
  • 交给状态机判断是否合法
  • 交给策略层做准入裁决
  • 把结果转换成动作派发到底层服务
  • 把关键过程沉淀成可观测信息

这样一来,随着需求加入,变化点有了清晰的归属:

  • 平台能力变化,落 Impl
  • 对象治理变化,落 Router
  • 状态合法性变化,落 FSM
  • 业务规则变化,落 Policy
  • 流程步骤变化,落 Workflow
  • 控制逻辑变化,才落 Coordinator Core

这才是真正可扩展的编排层规划方式。


八、工程化价值:这套架构真正换来的是什么

如果只是讨论"设计优雅",这套架构的说服力其实并不强。因为任何抽象都可以被质疑为"增加样板代码"。

但从工程化视角看,它换来的东西非常具体。

1. 编译期解耦

上层依赖协议,而不是具体实现类,这意味着:

  • 模块依赖图更稳定
  • 头文件污染更少
  • 子模块或二进制化演进空间更大
  • 能力层与业务层边界更容易被长期守住

这不是形式主义,而是大型工程演进的基础。

2. 运行时一致性

对状态型服务而言,是否共享同一实例、是否由同一层装配、是否有统一生命周期策略,直接决定系统是否稳定。

Router 的收口价值就在这里。

3. 可测试性

当协议边界明确之后,Coordinator 可以被测试成一个纯编排对象。

你可以非常自然地注入 fake service,验证:

  • 进入区域后是否经过防抖
  • 多车时是否正确拒绝
  • 未授权是否中止流程
  • 超时是否进入冷却期
  • 成功后是否记录指标

这让复杂异步系统第一次变得可验证,而不是只能依赖真机和日志。

4. 可观测性

复杂链路最怕"执行了,但说不清为什么"。

TelemetryService 配合 Coordinator 的状态收口,能天然形成一层"诊断控制面":

  • 当前状态是什么
  • 最近一次拒绝原因是什么
  • 本次扫描耗时多少
  • 连接失败的原因是什么
  • 当前是否处于冷却期
  • 最近的功耗指标是否异常

一旦接入调试面板,这套系统的解释力会得到质变。

5. 团队协作边界

成熟架构的价值之一,就是能把"谁负责什么"说清楚。

在这套结构下,职责天然可以分层:

  • 基础能力团队维护 Impl
  • 架构层维护 Router、Coordinator Core 与 FSM
  • 业务团队扩展 Policy 与 Workflow
  • 诊断能力接 Telemetry 与 Debug Panel

架构一旦形成,协作也会随之稳定。


九、任何架构都有代价,但真正该问的是:它买来了什么

必须承认,这套结构是有成本的:

  • 需要更多样板代码
  • 需要更清晰的边界意识
  • 新同学需要时间理解依赖流向
  • 抽象做不好会显得"重"
  • Router 可能退化成黑洞
  • Coordinator 可能膨胀成上帝对象

但复杂系统里,真正应该问的问题不是"有没有成本",而是"成本换来了什么"。

如果没有这套结构,你依然会付出代价,只不过代价会在运行时以另一种形式出现:

  • 偶发现象越来越多
  • 链路问题越来越难解释
  • 对象状态越来越不一致
  • 回调关系越来越模糊
  • 新增需求越来越不敢动
  • 线上问题越来越靠人肉排查

也就是说:

架构不是消灭复杂度,而是决定复杂度在什么时候、以什么形式被支付。

我的判断是:

对于近场感知、蓝牙、后台唤醒、遥测这类能力型系统,把复杂度前移到设计期,是一笔非常值得的交易。


十、结语:真正成熟的架构,不是层次多,而是控制力强

回到最开始的问题:

为什么我会在这类 iOS 项目里坚持 Protocol + Impl + Router + Coordinator?

因为它提供的,不只是"分层"这么简单。

它真正建立的是一种复杂系统的治理秩序:

  • 用 Protocol 固化能力边界
  • 用 Impl 吸收平台复杂度
  • 用 Router 收口对象治理与生命周期
  • 用 Coordinator 统一状态推进与决策权
  • 用 FSM + Policy + Telemetry 建立确定性与可观测性

在简单系统里,这些设计可能显得克制过度;

但在复杂系统里,它们恰恰是在抵御失控。

所以对我来说,这套架构最有价值的地方,从来不是"更优雅",而是:

它把那些本该在运行时爆炸的问题,提前收束到了设计时解决。

这,才是架构真正的控制力。

相关推荐
K成长日志2 天前
BLE Host层L2CAP--数据包格式
网络·物联网·无线通信·蓝牙·iot·ble
byte轻骑兵8 天前
【PBAP】规范精讲[3]: 蓝牙PBAP协议应用层核心——电话本数据的格式与交互逻辑解析
物联网·蓝牙·pbap·短距离通信协议
K成长日志20 天前
BLE链路层-隐私保护
网络·物联网·蓝牙·iot·ble
K成长日志20 天前
BLE链路层-Feature Support
网络·嵌入式·无线通信·蓝牙·iot·ble
民乐团扒谱机21 天前
【手把手通信仿真】FSK与GFSK调制原理对比及MATLAB全实现,附频谱波形分析
matlab·蓝牙·微电子·通信技术·通信原理·gfsk·fsk
EthanChou202022 天前
蓝牙学习之蓝牙框架
框架·蓝牙
律宏阔1 个月前
Android 车载蓝牙开发笔记:HFP、A2DP、AVRCP、PBAP、MAP、BLE 与系统 API
android·蓝牙
eamon1001 个月前
解析RoyalTek RBT-2100 蓝牙GPS接收器数据包
蓝牙·gps
K成长日志1 个月前
BLE不可连接状态--广播态
物联网·网络协议·蓝牙·低功耗·iot·ble