一、为什么 View 层架构值得单独谈
View 层(在 iOS 里常以 UIViewController + UIView 为核心,在 Android 里是 Activity / Fragment + View,在跨端里是 Page/Screen + Widget)是离产品形态最近、和业务耦合最深的一层。它往往具备三个特点:
- 定型后难改:发版后大规模调整 View 结构,牵动的业务面最大。
- 直接影响迭代效率:同样的需求,若 View 层杂乱、依赖横切、缺少工具与规范,业务开发会大量时间耗在"找代码、解冲突、改连锁反应"上。
- 是其它分层能否落地的"门面":网络、持久化、组件化做得再好,若页面层一团糟,整体架构仍会显得不可维护。
因此,View 层的目标可以概括为:在符合平台范式的前提下,让业务同学少写样板代码、少踩协作坑,并让后续拆分与演进成本可控。
下面用一张总览图概括**"数据与界面"**在典型移动应用中的流向(可以理解为数据管理者 / 加工者 / 展示者 三分法):

二、常见 View 层问题(与迭代速度的关系)
结合工程经验,下面几类问题最常拖慢迭代:
| 问题 | 表现 | 后果 |
|---|---|---|
| 代码混乱、无固定分区 delegate、事件、私有方法散落 | 阅读成本高,改动易漏 | |
| 过度继承 | 深层 VC/View 基类树 | 集成 Demo、单测、组件复用都变难 |
| 模块化不足 | 大文件、万能 VC | 无法并行开发,复用差 |
| 横向依赖 | A 业务直接 import B 的页面 | 编译耦合、联调阻塞、改名雪崩 |
| 缺少传承与规范 | 每人一种写法 | 架构被"腐蚀",无法演进 |
结论:View 层要同时解决怎么写(规范)、怎么拆(模块与模式)、怎么连(跨业务调用)三件事。
三、View 代码如何组织:文件内分区与职责
3.1 核心原则
- viewDidLoad(或等价入口):以组装视图树为主(addSubview / addArrangedSubview 等),避免堆一大坨初始化逻辑。
- 布局:见下文
分区顺序建议:
- Lifecycle
- 各 protocol 的 delegate/dataSource 实现(每个 #pragma mark / // MARK: 带协议名)
- 事件响应(target-action、手势、按钮等)
- Private helpers 尽量少;能外提则外提
- Getter / 懒加载属性 放文件后部(避免挡在主逻辑前)
3.2 Objective-C 示例(懒加载 + 分区)
objectivec
#pragma mark - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self.view addSubview:self.titleLabel];
[self.view addSubview:self.actionButton];
[self applyLayout];
}
#pragma mark - UIButton actions
- (void)onActionTapped {
// ...
}
#pragma mark - Private
- (void)applyLayout {
// 使用 Auto Layout / Masonry / 布局工具
}
#pragma mark - Getters
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [[UILabel alloc] init];
_titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2];
}
return _titleLabel;
}
3.3 Swift 等价写法(lazy var + MARK)
swift
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(titleLabel)
view.addSubview(actionButton)
applyLayout()
}
// MARK: - Actions
@objc private func onActionTapped() { }
// MARK: - Private
private func applyLayout() { }
// MARK: - Subviews (lazy)
private lazy var titleLabel: UILabel = {
let v = UILabel()
v.font = .preferredFont(forTextStyle: .title2)
return v
}()
要点:把"生产子视图"与"挂载到层级"在视觉上分开,长列表属性时文件仍可读;业务逻辑尽量不要和"创建 UILabel 属性"揉在 viewDidLoad 里。
3.4 关于布局写在哪
在 Auto Layout 场景下,在 viewWillAppear 里反复改 frame / 加约束容易和系统布局周期打架。更稳妥的做法是:约束在 viewDidLoad 里集中添加一次(或封装为 layoutPageSubviews()),或在 viewDidLayoutSubviews / updateConstraints 等系统约定的更新点做增量更新(注意避免重复添加相同约束)。
SwiftUI / Compose:声明式布局由运行时根据状态重算,传统"在哪一帧改 frame"的问题被框架吸收,但状态拆分与副作用边界仍然等价重要。
四、布局工具:可读性即生产力
裸 CGRectMake 可读性差;原生约束代码冗长。工程上常见做法:
- iOS:SnapKit / Masonry;或自研 UIView 布局扩展(链式、相对关系命名清晰)。
- Android:ConstraintLayout + ViewBinding;或 Compose 的 Modifier。
原则:布局 API 应能表达相对关系(相对父视图、相对兄弟视图),而不是魔法数字堆砌。
五、Storyboard / XIB / 代码 / 声明式 UI:怎么选
从协作冲突、需求变更、复杂动画三点来看倾向代码构建 UI:
| 方式 | 适合 | 风险 |
|---|---|---|
| Storyboard | 原型、单人小项目 | 合并冲突、多页混杂难协作 |
| XIB | 单块复杂控件、设计稿强绑定 | Outlet 易与重构不同步 |
| 代码 UIKit | 中大型团队、高频迭代 | 样板代码多,靠工具缓解 |
| SwiftUI / Compose | 新模块、状态驱动界面 | 与 UIKit/AndroidView 互操作需规范 |
团队规则示例:业务页面默认代码(或声明式);仅对稳定、复用度高的组件保留 XIB/SB;跨业务模块禁止在 Storyboard 里堆多页流程。
六、是否强制统一继承 BaseViewController?
能用组合与切面解决的,不必用继承堆环境依赖。
6.1 继承的问题
- Demo / 独立流程要接主工程时,被迫改基类、拉全量依赖。
- 新人默认写 UIViewController 会与规范冲突。
- 基类膨胀后,成为第二个"上帝类"。
6.2 替代:AOP + Category / Extension
目标 :业务代码使用平台原生类型;进入主工程后框架自动生效。
Method Swizzling(或 Aspects 类库)钩住 viewDidLoad / viewWillAppear 等,做埋点、统一导航栏、权限检查等。
Extension 提供工具方法,而非在基类塞功能。
objectivec
// 极简示意:在 +load 中对 UIViewController 做 swizzle(真实项目需防重复、防子类误伤)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [UIViewController class];
// swap viewDidLoad -> xxx_viewDidLoad 等
});
}
Swift 侧更推荐显式协议默认实现 + 在特定基类模块外仍保持可选,或依赖中间件/容器(如统一导航协调器),减少黑魔法。
七、模式梳理:MVC、MVCS、MVVM、VIPER
7.1 iOS 里 MVC 的再理解
服务端 MVC 里,"View"常常是描述视图的字符串;浏览器才是真正的 View。
在客户端,容器 + 子视图 + 事件回传必须由应用自己完成,苹果把容器放在 UIViewController.view 里是合理默认。
可记:C 管容器与调度;V 表达与弱反馈;M 提供数据与持久化接口。
7.2 MVCS
把 Store(数据存取) 从臃肿的 C 中抽出,适合瘦 Model + 集中存储路线。
7.3 MVVM(关键在 ViewModel,不在某个响应式库)
- ViewModel:把 Raw Data 加工成可直接驱动 UI 的状态(格式化文案、Section 模型、MKAnnotation 列表等)。
- Controller 仍存在:常见关系是 View ↔ C ↔ ViewModel ↔ Model(有人戏称 MVCVM)。C 负责绑定关系与导航,而不是"消失"。
- Reactive:RxSwift /Combine / 响应式库是绑定手段,不是 MVVM 的定义;无响应式也可用 delegate、closure、Observation 完成单向 / 双向数据流。
Swift 简例(结构示意)
swift
final class ProfileViewModel {
struct State: Equatable {
var title: String
var avatarURL: URL?
}
private(set) var state: State
init(user: User) {
self.state = State(title: user.displayName, avatarURL: user.avatar)
}
}
final class ProfileViewController: UIViewController {
private let vm: ProfileViewModel
init(vm: ProfileViewModel) {
self.vm = vm
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
override func viewDidLoad() {
super.viewDidLoad()
render(vm.state)
}
private func render(_ state: ProfileViewModel.State) {
title = state.title
// 加载头像等
}
}
7.4 VIPER
把交互、展示、路由再细分,适合超复杂模块;成本高,需团队纪律配合,否则容易过度设计。
7.5 横向对比

八、"拆分"心法
- 保留协调 V 与 M 的职责在 C(或 Presenter),其余能拆则拆。
- 拆出的模块提高复用与抽象:重复出现的 tableView 数据源、表单校验、地图标注组装等,独立成类型。
抽象粒度:对外参数少、语义清晰;若业务分支多,用策略/插件收拢调度,避免 VC 里长 if-else。
8.1 策略模式:消化"小粒度模块组合"

swift
enum MessageSendStrategy { case text, image, voice }
protocol SendStrategy {
func send(_ message: BaseMessage, completion: (Result<Void, Error>) -> Void)
}
final class MessageSender {
private let strategies: [MessageSendStrategy: SendStrategy]
init(strategies: [MessageSendStrategy: SendStrategy]) {
self.strategies = strategies
}
func send(_ message: BaseMessage, strategy: MessageSendStrategy,
completion: @escaping (Result<Void, Error>) -> Void) {
guard let s = strategies[strategy] else {
completion(.failure(NSError(domain: "msg", code: -1)))
return
}
s.send(message, completion: completion)
}
}
Controller 只保留一行调度:messageSender.send(msg, strategy: image),复杂链路下沉到策略内部。
九、跨业务页面调用:依赖下沉与 Mediator / Router
多业务 App 里,禁止业务模块间直接引用对方 VC;应通过路由/中介统一解析 URL 或注册表,返回所需对象或协调导航。

能力要求 :
请求协议与业务无关(如 open("app://order/detail?id=1") 或类型安全的路由枚举)。
可扩展为跨 App 与 Universal Link 同一套解析(注意返回值:URL 路由常不返回对象,内部模块路由可以)。
十、面向 Android / 跨端的一句对齐
- Android:Activity / Fragment 对应 VC 角色;ViewModel + UI State 对应 MVVM;导航用 Jetpack Navigation;模块间用 Deep Link / 接口下沉到 core。
- Flutter:Widget 树 + ChangeNotifier / Bloc / Riverpod 等,同样是状态与视图分离、路由中心化。
- 心法(少继承、多组合、协调层瘦身、跨模块不直连)平台无关。
十一、总结清单(Checklist)
- 规范:文件分区、命名、懒加载/子视图创建方式、布局 API 统一。
- 模式:以 MVC 为底色,按复杂度叠 MVCS / MVVM;VIPER 慎用但要知道适用边界。
- 工具:布局库、路由、埋点/AOP、设计系统组件。
- 协作:大中团队优先代码或声明式 UI;SB 谨慎;跨业务必走 Mediator。
- 哲学:架构服务业务,而不是让业务给架构打工;接口越"傻瓜"、越稳。
十二、参考
实践时请结合你们栈(UIKit / SwiftUI / Compose / Flutter)把**"协调层 + 状态 + 导航 + 模块边界"**四条线画清楚,比死记缩写更重要。