iOS架构设计与优化(三)

之前介绍了在项目层面如何去进行数据解耦和服务中间件的实现,这篇主要介绍在MVC模块内部如何不用MVVM而达到MVVM解耦的效果

我们都知道,MVVM设计模式是在MVC使用过程中日益庞大而不易维护的背景下应运而生的,设计思想和目的都是为C减负,将C中的业务和一些能抽出的逻辑代码抽出到VM中处理,使整体项目结构达到低耦合、高内聚

废话不多说,还是用一个实例来对比说明

页面为一个常规列表页,列表中展示的为订单信息,cell样式根据订单状态可能有两种样式,一种需要付款的状态cell样式,另一种不需要付款的状态cell样式,两种样式的cell高度不同

一种MVVM的实现

// Model
struct OrderSummaryModel {  
    var orderNumber: String?
    var orderState: Int?
    var orderDescirption: String?
    var orderAmount: String?
    var orderCreateDate: String?
    var orderNeedPay: Bool? = false
    ...
}

// View
class OrderCommonCell: UITableViewCell {

    //MARK: Property
    var orderNumbrerLabel: UILabel!
    var orderStateLabel: UILabel!
    var orderDesLabel: UILabel!
    var orderAmountLabel: UILabel!
    var orderDatelabel: UILabel!

    private var separatorLine: UIView!
    ...

    //MARK: Init
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupSubViews()
        setupConstraints()
    }

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

class OrderWaitingPayCell: UITableViewCell {

    //MARK: Property
    var orderNumbrerLabel: UILabel!
    var orderStateLabel: UILabel!
    var orderDesLabel: UILabel!
    var orderAmountLabel: UILabel!
    var orderDatelabel: UILabel!

    private var separatorLine: UIView!
    ...

    //MARK: Init
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupSubViews()
        setupConstraints()
    }

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

// VM
struct OrderViewModel {

    var orderInfo: OrderSummaryModel
    var orderCreateDate: String? {
        return orderInfo.orderCreateDate.convertToDateString()
    }

    init(orderInfo: OrderSummaryModel) {
        self.orderInfo = orderInfo
    }

    func updateCommonCell(cell: OrderCommonCell) {
        cell.orderStateLabel.text = order.orderState
        cell.orderAmountLabel.text = order.orderAmount
        cell.orderDesLabel.text = order.description
        cell.orderNumbrerLabel.text = order.orderNumber
        cell.orderDateLabel.text = orderCreateDate
        ...
    }

    func updateWaitingPayCell(cell: OrderWaitingPayCell) {
        cell.orderStateLabel.text = order.orderState
        cell.orderAmountLabel.text = order.orderAmount
        cell.orderDesLabel.text = order.description
        cell.orderNumbrerLabel.text = order.orderNumber
        cell.orderDateLabel.text = orderCreateDate
        ...
    }
}

由于此处的ViewModel只是为了简单的示意,所以没有添加很多代码,实际中要比此处的ViewModel可能要复杂的多。我们来看看此处ViewModel的作用,含有一个模型属性,然后配置cell的方法,还有就是一些模型中我们需要的属性,但不能直接使用,需要手动添加方法或属性进行一次转换,比如此处用了一个orderCreateDate属性来对服务器传过来的时间戳转换成我们需要的时间格式

再来看看控制器中的部分代码:

// Controller

//MARK: UITableViewDataSource
extension OrderCommonVC: UITableViewDataSource {  
    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return orderCellRowsNumberPerSection
    }

    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let viewModel = dataArray![indexPath.row]

        if let _ = viewModel.orderInfo.orderNeedOperation {
            let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(OrderWaitingPayCell.classForCoder())) as! OrderWaitingPayCell
            viewModel.updateCommonCell(cell: cell)
            return cell

        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(OrderCommonCell.classForCoder())) as! OrderCommonCell
            viewModel.updateWaitingPayCell(cell: cell)
            return cell
        }
    }

    public func numberOfSections(in tableView: UITableView) -> Int {
        return dataArray!.count
    }
}

在使用MVVM方式后,在UITableViewDataSource的cellForRow方法中我们就不再直接使用模型Model,而是使用定义的ViewModel,ViewModel中提供了更新cell的方法和数据处理等逻辑操作。至此,一种MVVM设计模式开发的订单列表代码开发完毕。此处需要再次说明的是,上面只是列出了一种MVVM模式,只是为了演示,如果是用MVVM+RAC或者MVVM+RX可能就不是这种写法,代码会更简洁,ViewModel中包含的逻辑处理会更多

接下来我们来看看不用MVVM,只用优化后的MVC如何实现

// View
protocol OrderCommonCellDataSource: AnyObject {  
    func orderNumber(forCell cell: OrderCommonCell) -> String
    func orderState(forCell cell: OrderCommonCell) -> String
    func orderDesription(forCell cell: OrderCommonCell) -> String
    func orderDate(forCell cell: OrderCommonCell) -> String
    func orderAmount(forCell cell: OrderCommonCell) -> String
}

class OrderCommonCell: UITableViewCell {

    //MARK: Property
    private var orderNumbrerLabel: UILabel!
    private var orderStateLabel: UILabel!
    private var orderDesLabel: UILabel!
    private var orderAmountLabel: UILabel!
    private.var orderDatelabel: UILabel!

    private var separatorLine: UIView!
    ...
    weak var dataSource: OrderCommonCellDataSource? {
        didSet {
            guard let dataSource = dataSource else { return }
            orderStateLabel.text = dataSource.orderState(forCell: self)
            orderAmountLabel.text = dataSource.orderAmount(forCell: self)
            orderDesLabel.text = dataSource.orderDesription(forCell: self)
            orderDateLabel.test = dataSource.orderDate(forCell: self)
            orderNumbrerLabel.text = dataSource.orderNumber(forCell: self)
            ...
        }
    }
    ...
}
////////////////////////////////////////////////////////////////// protocol OrderWaitingPayCellDataSource: AnyObject {
    func orderNumber(for cell: OrderWaitingPayCell) -> String
    func orderState(for cell: OrderWaitingPayCell) -> String
    func orderDesription(for cell: OrderWaitingPayCell) -> String
    func orderAmount(for cell: OrderWaitingPayCell) -> String
    func orderDate(forCell cell: OrderWaitingPayCell) -> String

    func orderCount(for cell: OrderWaitingPayCell) -> String
    func orderNeedOperationName(for cell: OrderWaitingPayCell) -> String
}
protocol OrderWaitingPayCellDelegate: AnyObject {  
    func orderWaitingPayCellDidClickToPay(cell: OrderWaitingPayCell)
}

class OrderWaitingPayCell: UITableViewCell {  
    //MARK: Property
    private var orderNumbrerLabel: UILabel!
    private var orderStateLabel: UILabel!
    private var orderDesLabel: UILabel!
    private var orderAmountLabel: UILabel!
    private var orderDatelabel: UILabel!

    private var separatorLine: UIView!
    ...
    weak var dataSource: OrderCommonCellDataSource? {
        didSet {
            guard let dataSource = dataSource else { return }
            orderStateLabel.text = dataSource.orderState(for: self)
            orderAmountLabel.text = dataSource.orderAmount(for: self)
            orderDesLabel.text = dataSource.orderDesription(for: self)
            orderNumbrerLabel.text = dataSource.orderNumber(for: self)
            orderDateLabel.test = dataSource.orderDate(forCell: self)
            ...
        }
    }
    ....
}

我们为cell抽出了一套DataSource协议,协议中包含cell所需要的数据获取方法,也可以按需抽出一套dellegate协议,这样cell就几乎按照UITableView的设计方式来设计了,cell不用关联和关心任何模型,只关心从数据源返回的数据

我们再来看Model,和Controller代码

// Model
class OrderSummaryModel {  
    var orderNumber: String?
    var orderState: Int?
    var orderDescirption: String?
    var orderAmount: String?
    var orderCreateDate: String?
    var orderNeedPay: Bool? = false
    ...
}

extension OrderSummaryModel {  
    func getOrderStateString(state: Int) -> String {
        switch state {
        case 1:
            return "已付款"
        case 2:
            return "已完成"
        ...
        default:
            return ""
        }
    }
}

extension OrderSummaryModel {  
    // 缓存行高
    var orderLineHeight: CGFloat {
        get {
            return orderNeedOperation ? orderWaitingPayCellHeight: orderCommonCellHeight
        }
    }
}

extension OrderSummaryModel: OrderCommonCellDataSource {  
    public func orderNumber(for cell: OrderCommonCell) -> String { return "订单编码:\(orderNumber)" }
    public func orderState(for cell: OrderCommonCell) -> String { return getOrderStateString(orderState) }
    public func orderDesription(for cell: OrderCommonCell) -> String { return "\(orderDescirption)" }
    public func orderDate(forCell cell: OrderCommonCell) -> String { return orderCreateDate.convertToDateString() }
    public func orderAmount(for cell: OrderCommonCell) -> String { return "¥ \(orderAmount)" }
}

extension OrderSummaryModel: OrderWaitingPayCellDataSource {  
    public func orderNumber(for cell: OrderWaitingPayCell) -> String { return "订单编码:\(orderNumber)" }
    public func orderState(for cell: OrderWaitingPayCell) -> String { return "\(orderState)" }
    public func orderDesription(for cell: OrderWaitingPayCell) -> String { return "\(orderDescirption)" }
    public func orderAmount(for cell: OrderWaitingPayCell) -> String { return "¥ \(orderAmount)" }

    public func orderCount(for cell: OrderWaitingPayCell) -> String { return "x1" }
    public func orderNeedOperationName(for cell: OrderWaitingPayCell) -> String { return getOrderStateString(orderState) }

}

// Controller

//MARK: UITableViewDataSource
extension OrderCommonVC: UITableViewDataSource {  
    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return orderCellRowsNumberPerSection
    }

    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let model = dataArray![indexPath.row]

        if model.orderNeedOperation!  {
            let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(OrderWaitingPayCell.classForCoder())) as! OrderWaitingPayCell
            cell.dataSource = self.dataArray![indexPath.row]
            return cell

        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(OrderCommonCell.classForCoder())) as! OrderCommonCell
            cell.dataSource = self.dataArray![indexPath.row]
            return cell
        }
    }

    public func numberOfSections(in tableView: UITableView) -> Int {
        return dataArray!.count
    }
}

我们在模型扩展中就直接实现了cell的数据源方法,在数据源方法中我们可以对模型中的元数据进行一些处理然后返回,也就是说在模型的扩展中实现了VM的功能。在控制器中,依然一样,直接将模型赋值给cell的dataSource。有两个细节,一个在模型,由于cell只有两种样式和两种特定高度,扩展模型实现了一个行高属性根据类型返回行高,这样每个模型中就已经包含了当前cell的高度。由于只有固定两种,此处意义并非十分大,如若碰到了需要动态计算行高的场景中,在模型此扩展中我们就可以在此做一些异步计算处理,提升性能。还有一个细节在cell,当我们在使用MVVM时,由于VM可能包含对cell的UI更新操作,如果直接通过属性设置的方式来更新,则我们就必须修改cell中需要设置的属性权限为internal才能访问,而通过抽出dataSource和delegate协议的方式来实现时就不需要暴露权限,属性的权限设置为private即可,更新UI的操作都在dataSource属性的didSet方法中执行。权限的缩小,在设计中无疑也是一种安全性优化。此处在模块内部这种权限控制的安全性体现的不是十分明显,但是在一些跨模块的设计中就十分有用了

到此,一个大致的通过优化MVC来实现MVVM功能,而不用新建文件和类的优化方案就大致介绍完毕。当然,需要再次说明的是此处只是举例了MVVM中的一种,提出的方案也只是提出一种优化MVC的思路

MVVM也好,MVP、VIPER也罢,不管哪种设计方案,没有最好的架构,只有最适合的架构