一: MVC:
ViewController响应View 事件,执行一些操作后,返回数据给View, View 拿到数据做刷新
class View: UIView {
var data: String? {
didSet {
lable.text = data
}
}
lazy var pushButton = UIButton(type: .system)
lazy var lable = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(pushButton)
pushButton.setTitle("前往详情", for: .normal)
addSubview(lable)
}
override func layoutSubviews() {
super.layoutSubviews()
lable.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 50)
pushButton.frame = CGRect(x: 0, y: 100, width: 100, height: 50)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController: UIViewController {
var data: String?
lazy var subView = View(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(subView)
subView.backgroundColor = .orange
subView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
subView.center = view.center
subView.pushButton.addTarget(self, action: #selector(pushDetailAction(_:)), for: .touchUpInside)
loadData()
}
@objc func pushDetailAction(_ sender: UIButton) {
print("前往详情页...")
}
private func loadData() {
DispatchQueue.global().async {
sleep(2)
DispatchQueue.main.async {
self.data = "哈哈哈"
self.subView.data = self.data
}
}
}
}
这么做有个问题:当这个页面很复杂的时候(比如播放页面,直播间页面等等),Controller要响应很多View的事件,做很多业务逻辑。慢慢的就搞成一两千行代码的臃肿,为了赶进度就这么写了,还有幸过了测试,但要后期维护,可能自己都头疼。这是见怪不怪的东西,刚出来工作的头两年,写过也接手这种代码,几十个 property,近3000行代码,调个布局都战战兢兢
改进1:
把View对应的业务放到View里面
class View: UIView {
var data: String? {
didSet {
lable.text = data
}
}
lazy var pushButton = UIButton(type: .system)
lazy var lable = UILabel(frame: .zero)
weak var controller: ViewController?
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(pushButton)
pushButton.setTitle("前往详情", for: .normal)
pushButton.addTarget(self, action: #selector(pushDetailAction(_:)), for: .touchUpInside)
addSubview(lable)
loadData()
}
override func layoutSubviews() {
super.layoutSubviews()
lable.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 50)
pushButton.frame = CGRect(x: 0, y: 100, width: 100, height: 50)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func pushDetailAction(_ sender: UIButton) {
controller?.pushDetail()
}
private func loadData() {
DispatchQueue.global().async {
sleep(2)
DispatchQueue.main.async {
self.data = "哈哈哈"
}
}
}
}
class ViewController: UIViewController {
lazy var subView = View(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(subView)
subView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
subView.center = view.center
subView.backgroundColor = .orange
subView.controller = self
}
func pushDetail() {
print("前往详情页...")
}
}
Controller 是很整洁,但View耦合了Controller,根本复用不了。当初真的是有小伙伴这么写。emmm
改进2:
class SubViewController: UIViewController {
var data: String? {
didSet {
lable.text = data
}
}
lazy var pushButton = UIButton(type: .system)
lazy var lable = UILabel(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pushButton)
pushButton.setTitle("前往详情", for: .normal)
pushButton.addTarget(self, action: #selector(pushDetailAction(_:)), for: .touchUpInside)
view.addSubview(lable)
loadData()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
lable.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 50)
pushButton.frame = CGRect(x: 0, y: 100, width: 100, height: 50)
}
@objc func pushDetailAction(_ sender: UIButton) {
print("前往详情页...")
}
private func loadData() {
DispatchQueue.global().async {
sleep(2)
DispatchQueue.main.async {
self.data = "哈哈哈"
}
}
}
}
class ViewController: UIViewController {
lazy var subViewController = SubViewController()
override func viewDidLoad() {
super.viewDidLoad()
addChild(subViewController)
let subView = subViewController.view!
view.addSubview(subView)
subView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
subView.center = view.center
subView.backgroundColor = .orange
}
}
把View替换成SubViewController,也有人这么写,业务倒是封装起来了,但是SubViewController的拓展性会变得很差
二:MVP
protocol PresentProtocol: NSObjectProtocol {
var view: ViewProtocol? { get set }
var data: String? { get set }
func loadData()
}
protocol ViewProtocol: ContextProtocol {
var present: PresentProtocol? { get set }
var context: ContextProtocol? { get set }
var data: String? { get set }
func refresh(data: String?)
}
protocol ContextProtocol: NSObjectProtocol {
func pushDetail(data: String?)
}
class Present: NSObject, PresentProtocol {
var data: String?
weak var view: ViewProtocol?
func loadData() {
DispatchQueue.global().async {
sleep(2)
DispatchQueue.main.async {
self.data = "哈哈哈"
self.view?.refresh(data: self.data)
}
}
}
}
class View: UIView, ViewProtocol {
var data: String?
lazy var pushButton = UIButton(type: .system)
lazy var lable = UILabel(frame: .zero)
var present: PresentProtocol? {
didSet {
present?.view = self
}
}
weak var context: ContextProtocol?
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(pushButton)
pushButton.setTitle("前往详情", for: .normal)
pushButton.addTarget(self, action: #selector(pushDetailAction(_:)), for: .touchUpInside)
addSubview(lable)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
lable.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 50)
pushButton.frame = CGRect(x: 0, y: 100, width: 100, height: 50)
}
@objc func pushDetailAction(_ sender: UIButton) {
pushDetail(data: self.data)
}
func refresh(data: String?) {
self.data = data
lable.text = data
}
func pushDetail(data: String?) {
context?.pushDetail(data: data)
}
}
class ViewController: UIViewController, ContextProtocol {
lazy var subView = View(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(subView)
subView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
subView.center = view.center
subView.backgroundColor = .orange
subView.present = Present()
subView.context = self
subView.present?.loadData()
}
func pushDetail(data: String?) {
print("前往详情页...")
}
}
这里就是通过协议确定职责。
对于Present来说,他只关心ViewProtocol的存在,响应ViewProtocol的调用,不管ViewProtocol是UIController,还是UIView。它的职责只是做数据的加工和传递,所以这里不必关心用户交互之类的UI事件。当然PresentProtocol 也可以被UIController实现,这样一来又变成了MVC下的万能Controller了。
对于View来说,它知道它需要显示哪些数据,和一个数据的提供者---这里就是Present。但是有一个操作它是不能或不属于它的事情。比如页面跳转,比如决定怎么显示/隐藏......这些都是Controller该做的事情,Controller本来就负责管理子视图的生命周期,显示状态。但是View怎么能知道它的Controller呢,通过属性传过来,通过其他方法获取都是不太好的,最好是我不知道,我只管有一个ContextProtocol接口就行,在View能力范围之外的事情,我都转发给Context。
对于Controller来说,它负责加载View,并决定View的显示和布局,给View关联一个Present,Controller的生命周期也即是View和Present的生命周期。
那这样做有什么意义的,代码量好像还增加了。
1、合理划分代码,给Controller减负
2、方便复用:假如DetailController,也用到了SubView的样式,但是数据不一样,怎么办呢,新建一个DetailPresent,实现PresentProtocol,View还是一样的View,Present换成DetailPresent就可以了
3、便于测试。
缺点呢,确实是要增加代码量
什么时候用:
我觉得当页面特别复杂的时候,比前面说的直播页面,视频播放页等等,特别有必要划分代码,虽然并不是一定要这么划分,或者说非MVP,但这是我目前感觉到提高项目可拓展可维护最有效的方式。也有人用继承来解决代码划分,比如把TableViewDataSource放在ViewController的基类里面,在继承这个基类之后确实省了部分代码,但是继承带来的副作用就是强耦合,不利于应对需求的变更。到时候如果还赶上进度紧的话,可能复制粘贴就完事了。总的来说在在一个复杂的场景下,面向协议比面向对象有更好的拓展性。
什么时候不用:
当然是简单页面或者说,我不确定页面会很复杂的时候。对于项目来说这只是一个方向不是一个标准,谁都不想多写代码,在项目前期,如果没有足够的经验,你即使以上帝视角你也看不出以后的项目发展。我就见过一个很不错的小伙伴,跟他合作,他会提醒我哪些该提出来,哪些很久都不会变动的,因为他确实开发过不少项目,见识的场景比较多,所以他的上帝视角比我高远得多。
跟其他模式比较:
比如MVVM:我看前端小伙伴的用的VUE,web本来就有表现html和行为js分离的规范,再加上双向绑定真的很省事。iOS这边有RAC RxSwift 也能做到 数据双向绑定,但是有很多 信号 管道 的概念,我没有深入使用过,其实mvp就是在多做了在没有MVVM的情况下,手动绑定View和Present。只是MVP需要的学习成本更低一些。
总结:
其实我觉得开发App都在做一件事情:当一个操作发生的时候,我该做什么,做到哪里为止。无论是MVX都是在做同样的实现。只不过我们追求的是代码更加规范和简洁。我刚入行的时候,对模式架构之类的概念模模糊糊的。后来一次聊天一个前辈说,不是有这么一句话么,没有什么事情不是加一层不能解决的,有的话就是在加一层。后来写多了,大概也理解他的意思了。一个几十个状态,上百个方法的类阅读起来真的头大,假如能更优雅一点为什么不呢。当然这只是理想状态。
网友评论