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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容