Swift MVVM 你喜欢哪种?
RAC or RXSwift?
今天的主题不是这些商业级应用框架,而是小而美的MVVM实现方案:Closure AND Protocol实现MVVM
1、Closure
运用闭包的方式,使用一个辅助工具,实现的MVVM,可以实现数据绑定,响应式编程的很大优点就是数据绑定。将主要的数据处理逻辑和请求处理都会放到viewModel里处理,辅助工具的主要任务是提供一个闭包来存储数据,在view里对闭包进行实现,viewModel里拿到数据的时候,调用闭包,实现数据的绑定。
辅助工具的实现如下:
class Observable<T>{
typealias Observer = (T) -> ()
var observer:Observer?
var value:T{
didSet{
observer?(value)
}
}
init(_ value:T) {
self.value = value
}
func observer(observer:Observer?){
self.observer = observer
observer?(value)
}
}
viewModel中的数据属性,都应该是Observable<T>类型的,T代表的是具体需要的数据的类型,在给value赋值的时候,就会对observer观察者进行调用,执行闭包,进行数据刷新。
比如Controller里有个HeadView,可以这样写HeadView的相关代码:
//View
class HeadView: UIView {
lazy var timeLabel:UILabel = {
let timeLabel = UILabel()
return timeLabel
}()
override init(frame: CGRect) {
super.init(frame: frame)
timeLabel.frame = frame
addSubview(timeLabel)
}
var headViewModel:HeadViewModel?{
didSet{
headViewModel?.timeText.observer {
[unowned self] in
self.timeLabel.text = $0
}
}
}
}
//Model
struct HeadModel {
let timeText:String
}
//ViewModel
class HeadViewModel{
var timeText:Observable<String>
init(_ model:HeadModel) {
self.timeText = Observable(model.timeText)
}
}
在Controller里,可以这样给HeadView的数据进行绑定,代码如下:
//这段代码里的viewModel是整个Controller的ViewModel,
func addHeadView(){
headView = HeadView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: headH))
//数据绑定
viewModel.headViewModel.observer{
[unowned self] headViewModel in
self.headView.headViewModel = headViewModel
}
view.addSubview(headView)
}
我这边应用的MVVM划分的比较细致,我把每个View都会给对应一个ViewModel,而且还会给ViewController对应一个ViewModel,包含所有它所拥有的View的ViewModel的数据。这样,可以让数据进行分级处理,例如:有些数据在ViewController需要稍微处理的原始数据,而在它的view的子view中需要复杂处理的数据。这个时候,就可以对不同的层次做不同的数据处理了,层次更加分明。
//比如一个Viewcontroller中有HeadView和TableView,ViewController的ViewModel会是这个样子
class ViewModel{
let cellDatas:Observable<[CellViewModel]>
let headViewModel:Observable<HeadViewModel?>
init() {
self.cellDatas = Observable([])
self.headViewModel = Observable(nil)
}
}
此外,我还会把数据请求和数据初步处理放倒这里面,相当于把ViewController的helper的功能给集成到了这个ViewModel里了。
class ViewModel{
let cellDatas:Observable<[CellViewModel]>
let headViewModel:Observable<HeadViewModel?>
init() {
self.cellDatas = Observable([])
self.headViewModel = Observable(nil)
}
//数据请求,大概就是这样,很多东西没处理,见谅😳
func requestData(completion:()->()){
self.getData { model in
let cellViewModel = model.cellModels.map({ cellModel in
return CellViewModel(cellModel)
})
headViewModel.value = HeadViewModel(model.headModel)
cellDatas.value = cellViewModel
completion()
}
//数据清洗
func dataCleaning(){
}
}
目前,在我的项目中,并没有大规模的使用的这样的方式,这个东西会大量地使用闭包,所以会有大量的闭包在堆里,造成内存增高。所以,推荐通用的View或组件会使用这种方式MVVM的方式,会很适合。
2、Protocol
基于Protocol来实现的MVVM解耦合,这个方式是以组合协议的方式来构成ViewModel。在这之前,需要先说明下组合的优点,一些老司机会说:组合优于继承。那么,相比较于组合,继承有什么缺点呢?
(1)继承会产生god class,这是代码中的毒瘤,试图集成很多的功能,像是ViewController基类中的loadView一样,当这个类的功能越来越多的时候,这个类就会变得难以维护,很明显的违反了我们代码中单一功能的原则。组合能够帮助我们去处这些god class。
(2)破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性。组合,每个都是功能点都是独立的。在Swift中,我们能使用Swift的特性-协议,来更好地实现组合。
(3)不支持动态继承,在运行时,子类无法选择不同的父类。而使用组合,我们可以选择我们想要的功能点。
所以,组合比继承更具灵活性和稳定性,在程序设计的时候优先使用组合会比较好。
在组合Protocol中,我会将一个试图拆分成更小的基本单位。例如,一个cell中有tltle、subtitle和image,就可以拆分出三个个协议出来,再给这几个协议添加默认实现,例如这样子的协议。
protocol LabelPresentable {
var title:String{get}
var titleColor:UIColor {get}
func updataLabelData(_ titleLable:UILabel)
}
extension LabelPresentable {
var titleColor:UIColor{
return UIColor.blue
}
func updataLabelData(_ titleLable:UILabel){
titleLable.text = title
titleLable.textColor = titleColor
}
}
protocol ImageViewPresentable {
var imageName:String{get}
func updataImageViewData(_ imageView:UIImageView)
}
extension ImageViewPresentable{
func updataImageViewData(_ imageView:UIImageView){
imageView.image = UIImage(named:imageName)
}
}
protocol SubLabelPresentable {
var subTitle:String{get}
var subTitleColor:UIColor {get}
func updataSublabelData(_ subTitleLable:UILabel)
}
extension SubLabelPresentable{
var subTitleColor:UIColor{
return UIColor.red
}
func updataSublabelData(_ subTitleLable:UILabel){
subTitleLable.text = subTitle
subTitleLable.textColor = subTitleColor
}
}
这样,一些公共的实现就添加到了协议里面,只要某个类遵守这些协议,就能够拥有这些属性和功能了。ViewModel是这样实现的:
protocol CustomCellProtocol: LabelPresentable,ImageViewPresentable,SubLabelPresentable{}
class CellViewModel:CustomCellProtocol{
var title: String
var imageName: String
var subTitle:String//比上面的多了一个属性
init(_ model:CellModel) {
//处理数据逻辑
self.title = model.lableOneText
self.imageName = model.imageAdress
self.subTitle = model.lableTwoText
}
}
先将需要用到的协议可以组合起来,形成一个大的协议CustomCellProtocol,实现协议必须要实现的属性。具体到cell中的实现,就很简单了,在cell中添加一个遵守CustomCellProtocol协议的属性,数据传递过来后,更新显示就好了:
var customCellModel:CustomCellProtocol?{
didSet{
customCellModel?.updataImageViewData(self.imageView!)
customCellModel?.updataLabelData(self.textLabel!)
customCellModel?.updataSublabelData(self.detailTextLabel!)
}
}
这样的MVVM将显示层拆分成粒子化的Protocol,构成更加复用的单个Protocol或者是组合 Protocol,可以将通用的Label或者是Image组合起来,每一个View都可以作为一个插件使用,极大地增加了view的复用性。适合于一些公用组建的抽取,模块化。但是,自我感觉,要大规模的应用还是要踩不少的坑的。我在写Demo的时候就踩了不少的坑,感觉坑还不会少😳。
目前,在我们项目中应用最多的还是那种负责数据逻辑处理,数据请求处理的ViewModel形式的MVVM,即相当于添加了一个helper。
上述两个MVVM的方式,我都写了Demo,放在我的GitHub上,有兴趣的可以去看看,欢迎pull request。https://github.com/chaiyanpu/SwiftMVVMDemo
参考:atswift-2016李洁信的分享
SwiftWeather:https://github.com/JakeLin/SwiftWeather