之前介绍了在项目层面如何去进行数据解耦和服务中间件的实现,这篇主要介绍在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也罢,不管哪种设计方案,没有最好的架构,只有最适合的架构