美文网首页
个人理解的MVC到MVP演变(iOS)

个人理解的MVC到MVP演变(iOS)

作者: cdf90488031a | 来源:发表于2020-05-18 05:15 被阅读0次

一: 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都是在做同样的实现。只不过我们追求的是代码更加规范和简洁。我刚入行的时候,对模式架构之类的概念模模糊糊的。后来一次聊天一个前辈说,不是有这么一句话么,没有什么事情不是加一层不能解决的,有的话就是在加一层。后来写多了,大概也理解他的意思了。一个几十个状态,上百个方法的类阅读起来真的头大,假如能更优雅一点为什么不呢。当然这只是理想状态。

相关文章

网友评论

      本文标题:个人理解的MVC到MVP演变(iOS)

      本文链接:https://www.haomeiwen.com/subject/xmryohtx.html