Swift MVVM 你喜欢哪种?

作者: DelegateChai | 来源:发表于2016-12-05 21:08 被阅读1090次

    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

    相关文章

      网友评论

        本文标题:Swift MVVM 你喜欢哪种?

        本文链接:https://www.haomeiwen.com/subject/jxhopttx.html