今天我才学会iOS的MVP写法

目前写 iOS 程序差不多整整两年了,期间断断续续写过 3 个 iOS 项目。之前写 android 的时候使用了 MVP 架构,但是写 iOS 的时候一直没有找到好的 MVP 实践,所以之前项目的所有代码都是堆在 ViewController 中。虽然使用了较多的注释和#MARK 使得代码不那么凌乱,但是动辄几百甚至上千行的 ViewController 还是让人很抓狂。

直到昨天我开始写下一个 iOS 项目的时候,开始之前又谷歌了一次“iOS MVP”,这次终于找到几篇文章,让我找到了一个简单易懂的 MVP 实践。:

iOS-mvp-sample

Don't Put View Code Into Your View Controller

以登陆页面为例。要实现一个最简单的登陆页面如下:

第一步,View

在之前的项目中,页面的 UI 代码都是写在 ViewController 中, 在本次实践中将 UI 代码抽离出来, 写在 LoginView.swift 中(只写了输入框和按钮):

import UIKit

protocol LoginViewDelegate: NSObjectProtocol {
    func loginWith(username: String, password: String)
}

class LoginView: UIView {
    var delegate: LoginViewDelegate?

    let usernameTf = UITextField()
    let passwordTf = UITextField()
    let loginBtn = UIButton()

    override init(frame: CGRect) {
        super.init(frame: frame)
        initView()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    convenience init() {
        self.init(frame: CGRect.zero)
        initView()
    }

    func initView() {
        self.backgroundColor = UIColor.white

        // 用户名输入框
        self.addSubview(usernameTf)
        usernameTf.borderStyle = .roundedRect
        usernameTf.snp.makeConstraints { (make) in
            make.centerX.equalToSuperview()
            make.top.equalToSuperview().offset(270)
            make.left.equalToSuperview().offset(40)
            make.right.equalToSuperview().offset(-40)
        }

        // 密码输入框
        self.addSubview(passwordTf)
        passwordTf.borderStyle = .roundedRect
        passwordTf.snp.makeConstraints { (make) in
            make.centerX.equalToSuperview()
            make.top.equalTo(usernameTf.snp.bottom).offset(16)
            make.left.equalToSuperview().offset(40)
            make.right.equalToSuperview().offset(-40)
        }

        //登录按钮
        Views.resetButton(loginBtn, title: "登录", color: UIColor.green, font: UIFont.systemFont(ofSize: 22))
        self.addSubview(loginBtn)
        loginBtn.addTarget(self, action: #selector(clickLoginButton), for: .touchUpInside)
        loginBtn.snp.makeConstraints { (make) in
            make.centerX.equalToSuperview()
            make.top.equalTo(passwordTf.snp.bottom).offset(20)
        }
    }

    @objc func clickLoginButton() {
        delegate?.loginWith(username: usernameTf.text ?? "", password: passwordTf.text ?? "")
    }
}
复制代码

如此只要在 ViewController 中实现 LoginViewDelegate 协议就可以捕获到登录按钮点击事件。

第二步,Presenter

presenter 有以下特点:

  • 处理用户交互的逻辑
  • 与 Model 层进行通信,将数据转换为 UI 友好的格式,并更新视图
  • 不依赖 UIKit

在这个例子中,presenter 只有一个执行登录请求的方法:

import Foundation

struct LoginSelfData {
    let username: String
    let password: String
}

protocol LoginSelf: NSObjectProtocol {
    func startLoading()
    func finishLoading()
    func loginSucceed()
    func loginFail(mes: String)
    func noNetwork()
}

class LoginPresenter {
    var loginSelf: LoginSelf?

    /// 登录请求,点击登录按钮时调用
    ///
    /// - Parameters:
    ///   - username: 用户名
    ///   - password: 密码
    func loginWith(username: String, password: String) {
        let parameter = ["username": username, "password": password]

        ...

        // 执行登录请求
        loginSelf?.startLoading()

        ...

        // 接收到返回数据
        loginSelf?.finishLoading()

        ...

        // 登录成功
        loginSelf?.loginSucceed()

        ...

        // 登录失败
        loginSelf?.loginFail(mes: "msg")
    }
}
复制代码

如此只要在 ViewController 中实现 LoginSelf 协议,就可以捕获到登录请求完成后的回调方法,这时就可以根据不同的回调方法更新视图。

第三步,ViewController

ViewController 中的代码很简单, 只要分别初始化 View 和 Presenter 并实现各自的协议就好。

import UIKit

class LoginViewController: UIViewController {
    let loginPresenter = LoginPresenter()
    var loginView: LoginView?

    override func viewDidLoad() {
        super.viewDidLoad()

        loginPresenter.loginSelf = self

        loginView = LoginView.init(frame: self.view.bounds)
        loginView?.delegate = self
        self.view = loginView

    }
}

// MARK: - 更新视图
extension LoginViewController: LoginSelf {
    func startLoading() {

    }

    func finishLoading() {

    }

    func loginSucceed() {

    }

    func loginFail(mes: String) {

    }

    func noNetwork() {

    }
}

// MARK: - 响应用户交互操作
extension LoginViewController: LoginViewDelegate {

    /// 登录
    ///
    /// - Parameters:
    ///   - username: 用户名
    ///   - password: 密码
    func loginWith(username: String, password: String) {
        loginPresenter.loginWith(username: username, password: password)
    }
}
复制代码

小结

如此 一个相对简单的 iOS MVP 实践就完成了,采取这样的写法之后顿时感觉代码一下子很清晰,可能总的代码量会多一点,但是单个文件肯定会比之前的胖 ViewController 少,不仅方便同事之间交流,也方便自己查找代码、 修改 bug。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值