前言
关于ViewController讨论的最多的是它的肥胖和臃肿,即使使用传统的MVC模式,ViewController也可以写的很优雅,这无关乎设计模式,更多的是你对该模式理解有多深,你对于职责划分的认知是否足够清晰。ViewController也从很大程度上反应一个程序员的真实水平,初级程序员他的ViewController永远是臃肿的、肥胖的,什么功能都可以往里面塞,不同功能间缺乏清晰的界限。而一个优秀的程序员它的ViewController显得如此优雅,让你产生一种竟不能修改一笔一画的感觉。
ViewController职责
- UI 属性配置 和 布局
- 用户交互事件
- 用户交互事件处理和回调
用户交互事件处理: 通常会交给其他对象去处理 回调: 可以根据具体的设计模式和应用场景交给 ViewController 或者其他对象处理
而通常我们在阅读别人ViewController
代码的时候,我们关注的是什么?
- 控件属性配置在哪里?
- 用户交互的入口位置在哪里?
- 用户交互会产生什么样的结果?(回调在哪里?)
所以从这个角度来说,这三个功能一开始就应该是被分离的,需要有清晰明确的界限。因为谁都不希望自己在查找交互入口的时候 ,去阅读一堆控件冗长的控件配置代码, 更不愿意在一堆代码中去慢慢理清整个用户交互的流程。 我们通常只关心我当前最关注的东西,当看到一堆无关的代码时,第一反应就是我想注释掉它。
基于协议分离UI属性的配置
enum TreeView {
case leaf(UIView)
case node(UIView, [TreeView])
@discardableResult func build() -> UIView {
switch self {
case .leaf(let view):
return view
case .node(let parent, let nodes):
nodes.forEach {
let nodeTree = $0.build()
if let stackView = parent as? UIStackView {
stackView.addArrangedSubview(nodeTree)
} else {
parent.addSubview(nodeTree)
}
}
return parent
}
}
var rootView: UIView {
switch self {
case .leaf(let view):
return view
case .node(let parent, _):
return parent
}
}
}
protocol PFMViewConfigurer {
var contentViews: TreeView { get }
func addSubViews()
func configureSubViewsProperty()
func configureSubViewsLayouts()
func initUI()
}
依赖这个协议就可以完成所有控件属性配置,然后通过extension protocol 大大减少重复代码,同时提高可读性
extension PFMViewConfigurer {
func addSubViews() {
_ = contentViews.build()
}
func configureSubViewsProperty() {
// default: not any thing
}
func configureSubViewsLayouts() {
// default: not any thing
}
func initUI() {
addSubViews()
configureSubViewsProperty()
configureSubViewsLayouts()
}
}
这里 我将控件的添加和控件的配置分成两个函数addSubViews
和configureSubViewsProperty
, 因为在我的眼里函数就应该遵循单一职责这个概念:
addSubViews
: 明确告诉阅读者,我这个控制器包含哪些控件
configureSubViewsProperty
: 明确告诉阅读者,控件的所有属性配置都在这里,想要修改属性请阅读这个函数
来看一个实例:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// 初始化 UI
initUI()
// 绑定用户交互事件
bindEvent()
// 将ViewModel.value 绑定至控件
bindValueToUI()
}
// MARK: - UI configure
// MARK: - UI
extension PFMAccountsViewController: PFMViewConfigurer {
var contentViews: TreeView {
.node(
view,
[
.node(
stackView,
[
.leaf(titleLabel),
.leaf(trailingIconImageView)
]
)
]
)
}
func configureSubViewsLayouts() {
stackView.anchor()
.topToSuperview(constant: Constants.space)
.leftToSuperview(constant: Constants.space)
.rightToSuperview(constant: Constants.space)
.bottomToSuperview(constant: Constants.space)
.activate()
}
}
由于我使用的是MVVM模式,所以viewDidLoad
和MVC模式还是有些区别,如果是MVC可能就是这样
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// 初始化 UI
initUI()
// 用户交互事件入口
addEvents()
}
// MARK: callBack
......
由于MVC的回调模式很难统一,有Delegate, Closure, Notification、KVC等,所以回调通常会散落在控制器各个角落。最好加个MARK
flag, 尽量收集在同一个区域中, 同时对于每个回调加上必要的注释:
- 由哪种操作触发
- 会导致什么后果
- 最终会通往哪里
所以从这个角度来说UITableViewDataSource
和 UITableViewDelegate
完全是两种不一样的行为, 一个是 configure UI , 一个是 control behavior , 所以不要把这两个东西写一块了。
总结
基于职责对代码进行分割,这样会让你的代码变得更加优雅简洁,会大大减少一些万金油代码的出现。减少阅读代码的成本也是我们优化的一个方向,毕竟谁都不想因为混乱的代码影响自己的心情