很多架构设计,只有在系统变复杂之后,才会显现出它真正的价值。
在业务简单的时候,直接写实现类、直接调单例、直接在回调里串逻辑,往往也能工作;甚至从短期交付视角看,这样做还更"高效"。
但一旦系统开始具备下面这些特征,原本那些"能跑就行"的设计,几乎都会在后续迭代中逐渐失效:
- 存在多个系统入口
- 存在长生命周期共享服务
- 存在异步事件驱动链路
- 存在大量边缘状态与竞争条件
- 存在安全兜底与策略演进需求
- 存在调试、回放、遥测与问题追踪诉求
我最近在做一个近场感知相关的 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、回放、调试与遥测
而近场感知系统恰好同时具备这些特征。
原因在于,它不是一个"功能点",而是一条"异步控制链":
- 系统感知事件进入
- 状态机确认是否接受
- 权限与策略层决定是否放行
- 底层能力服务执行扫描与连接
- 结果反馈回控制面
- 控制面决定是否推进、回退、降级或拒绝
- 观测层记录全过程
这类链路最怕的,不是多一层抽象,而是没有控制面。
四、架构图:从能力堆叠到治理闭环
下面这张图更适合用来理解这套结构的"治理含义",而不只是"模块结构"。
如果只看技术栈,这不过是定位、蓝牙、埋点和权限服务的组合;但如果从治理视角看,它已经形成了一个完整闭环:
Protocol让能力边界稳定Impl让平台复杂度下沉Router让对象治理统一Coordinator让控制决策集中FSM + Policy + Snapshot让状态确定、规则可演进、过程可观测
这才是这套架构最深层的价值。
五、时序图:控制面如何统一接管整条能力链
再看一次完整链路,会更容易理解 Coordinator 的地位。
这里最重要的一点,不是流程本身,而是"权力归属":
- 触发事件来自系统
- 执行动作来自底层服务
- 但链路推进权与拒绝权必须掌握在
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 InputFSMPolicy SetAction DispatcherTelemetry / 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建立确定性与可观测性
在简单系统里,这些设计可能显得克制过度;
但在复杂系统里,它们恰恰是在抵御失控。
所以对我来说,这套架构最有价值的地方,从来不是"更优雅",而是:
它把那些本该在运行时爆炸的问题,提前收束到了设计时解决。
这,才是架构真正的控制力。