ReactiveCocoa相关Swift相关swift学习

Swift MVVM 你喜欢哪种?

2016-12-05  本文已影响1090人  DelegateChai

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

上一篇下一篇

猜你喜欢

热点阅读