美文网首页iOS 杂谈iOS 实用技术iOS之框架架构
基于RxSwift的MVVMR架构(一)思想、基本原理及初步实现

基于RxSwift的MVVMR架构(一)思想、基本原理及初步实现

作者: Tangentw | 来源:发表于2017-02-27 15:17 被阅读1749次

    摘要

    本系列文章将详细分析iOS的MVVMR架构模式,并基于Swift的响应式、函数式编程框架RxSwift提供相应的实现。
    系列共分为两个部分:

    1. MVVMR架构的思想、基本原理及其初步实现
    2. 架构中某些细节部分的实现封装以及实战

    这篇文章讲述的是第一部分的内容,我会先把架构的各个组成粗略地罗列出,然后再对它们进行详细的分析,最后结合代码进行实现。若文章中存在模糊或不合理的地方,还请各位包涵,也欢迎大家向我进行反馈。

    前言

    寒假期间,我和小组的伙伴们针对团队之前的一款还未上线的项目进行大重构,开发语言从Objective-C转到了Swift,作为组长,我负责了整个项目架构的搭建。在旧项目中,我是基于ReactiveCocoa搭起了MVVM这种架构模式。鉴于此架构模式优点颇多,并且在前面也已实践过,所以在新项目里我也依旧沿用了MVVM,不过用于事件以及数据绑定的框架就改用了RxSwift。关于RxSwift,我在之前也写过一篇文章: RxSwift进阶与实战 ,这篇文章的实战部分也提供了一个简单的MVVM架构模式实现。

    分析下现在移动端开发比较热门的几款架构模式:MVC, MVP, MVVM, VIPER,除去最经典的模式MVC外,其余的模式究其根本,其实都是从MVC衍变而来,并且都是针对其中的Controller层进行分层再细化,而本文所针对的MVVM架构模式,原理上它为了减轻MVC架构中Controller层的业务负担,将Controller再细分为两个部分,一部分跟原本的View层合并在一起形成新的View层,另一部分就变成了ViewModel层。(这里所说的Controller并不能指代iOS中的UIViewController)对于我来说MVPMVVM极其相似,如果偏要让我说出它们的不同,我会主观地认为MVPPresentation做的事情只是数据与事件的解析转换等业务逻辑,而MVVMViewModel中除了这些业务逻辑外,里面还可以存有某些状态变量,像在RAC中,专门有一个宏用于状态变量与信号的绑定:RAC(viewModel, userToken) = userTokenSignal,而RxSwift我个人认为是倾向于流的转换,尽量避免出现状态变量。所以重构项目中有时候我会想: "我TM是在写MVVM还是MVP?!",算了,不要在意这些细节...

    在上面我一直都在说MVVM模式,但是文章的标题呈现的却是MVVMRR其实是我“自作主张”增添的Router(路由器),这个设计在下面会说到。

    接下来我就本此项目的架构,向大家进行详细的分析。

    MVVMR基本蓝图

    首先给大家看两张示意图:


    基本构成 事件、数据流转换

    这两张图分别表示了MVVMR架构中的基本构成要素和流转换示意。
    在图从,我们可以看到整个架构的组成要素分别是:View(视图)ViewModel(视图模型)Input(输入)Output(输出)Router(路由器)UnitCase(单位实例), 其中,ViewInput同组成View层,ViewModelOutput组成ViewModel层,UnitCase为一个简单的数据结构,用于保存ViewViewModel的关系,用于后期绑定器对它们相互间进行绑定,这个在后面会详细说到。

    接下来有两方面需要提及:

    架构目的

    前面说到,传统的MVC架构中,ViewController由于要处理过多的业务逻辑以及对View层的显示逻辑,会变得越来越复杂,最后将会成为一个重量级角色,在开发中容易乱了手脚,并且严重缺乏可维护性。MVVM架构致力于减轻ViewController层的负担,将一部分属于纯业务逻辑处理放到了ViewModel中,而对于View的显示逻辑,如UIView布局渲染、动画等就一并归于View层。

    View : 视图的布局、渲染、动画、UIViewController的转场

    ViewModel : 纯业务逻辑处理

    Model : 提供数据,如网络请求数据,本地数据库、UserDefaults

    有一点需要注意的是,因为MVVM比起MVC来说在层级的数量上有所增加,所以我们需要再从原来的基础上多维护了某些东西,这很容易造成架构中耦合度的上升,为了降低耦合,我在架构中引入了Input以及Output的概念,后面有详细的分析。

    架构思想

    整套架构围绕着的一个思想是: 事件与数据基于流的抽象
    我们把事件(如用户的触发事件)以及数据(网络、本地数据)抽象成在一条在管道中流动的,每一次的业务处理,都像是一个接入了这条管道中的流处理器,将流入的流转换加工,并输出处理过后的流。因为事件或数据可能会涉及多个不同的业务处理,所以在管道中也可以接入多个流处理器,让事件和数据在管道中流动的时候发生连锁反应。

    流思想
    在本架构中,RxSwift框架就是这条包裹着事件与数据流的管道。

    各模块详解

    接下来我就MVVMR架构中各重要模块的概念,结合iOS的实际开发来详细说明。

    View

    View层做的东西都只是跟视图有关系,布局、渲染、动画等等,并不会接触与业务相关的内容。它汇总视图的触发事件或数据,构建出Input传入ViewModel,并接受ViewModel传过来的Output,刷新视图显示。
    架构中,我把UIViewController也归入了View层中,因为个人觉得ViewController与视图有着非常密切的关联,若要强制性分离职责,应该将ViewController里面的所有业务逻辑抽离出来,让UIViewController其只充当View的一部分。
    View中还持有路由器Router,用于视图的跳转。(接下来会说到)

    ViewModel

    说简单点,ViewModel做的事情就只有一件: 转换,说复杂点,ViewModel需要将View传入的Input中所有的事件数据流进行转换处理,最终将完成处理后的流放入Output中传递给View,所有的业务逻辑都是在这里进行实现,其中涉及到数据的请求(网络、本地)需要向Model请求获取。

    Model

    模型层,数据提供者。提供网络请求数据,本地数据库、UserDefaults缓存数据,支持对数据进行解析处理。不过在实际项目中,Model层并没有很明显地表示出来,我是将网络请求、JSON数据解析和本地缓存封装在一起构建出一个较为强大的“流转换器”,其也算是Model层的一部分。相关网络请求的封装我会在下一篇中谈到。

    Input & Output

    InputOutput其实是一个容器,里面装载着各种事件数据流,在ViewViewModel通信中起到传递的作用。
    看到这里,可能有人会认为,ViewViewModel之间的相互通信较为简单,只需通过方法去调用即可,没必要又另外再构建多一个InputOutput。其实,在框架设计中,我构建了这两个东西,主要目的就是实现View层与ViewModel层的完全解耦。架构在工作的时候,View以及ViewModel各自维护着自己的运作,且它们之间不存在过多的耦合,即两层之间互不关注,也互不知道对方的实际情况,它们间的通信只依赖于InputOutput。这样,在开发以及后期的维护中,我们在对其中一层进行修改或重构时,另外一层可完全不需要改动。
    在实际项目中,我使用的是Swift的struct(结构体)去实现InputOutput,并将每个事件数据流作为结构体中的属性来持有。这样做的好处是每个事件数据流都能清晰明了地列举在代码中,通过观察Input的属性,我们能知道View能够产出多少种视图触发的事件数据流,通过观察Output的属性,我们也得知View最终要接收哪些更新视图的事件数据流。

    这里总结下设计InputOutput的目的:

    • 实现ViewViewModel层之间的解耦
    • 能够清晰罗列出各种事件数据流

    Binder & UnitCase

    首先来说下Binder(绑定器),它要做的事情就是将View以及ViewModel进行绑定。这里就抛出了一个问题: 我们为什么需要绑定?
    我们知道,iOS应用是以页面为单元的,一个页面就是一套MVCMVVM工作的结果。而普通的应用本身是拥有非常多的页面,若我们使用的架构模式是MVVM,这就需要创建同等数量的若干套MVVM,而MVVM的构建需要将各层各模块联系绑定在一起。若每次我们在需要跳转到一个新页面时才去对架构进行绑定,这就增大了代码的复杂度以及冗余度,所以,我们需要一套机制,在我们需要呈现一个新页面时,自动帮我们将架构各层各模块进行绑定。在MVVMR架构中,我使用的是Binder来实现这种机制。
    但是,在绑定时,我们必须要将ViewViewModel一一对应起来,不可能说将一个页面的View跟其他页面的ViewModel进行绑定。所以,为了明确这种一一对应关系,我引入了UnitCase,它是一个存有ViewViewController对应关系的容器。通过UnitCaseBinder就能正确绑定ViewViewModel层。

    Router

    路由器就是为了实现各页面之间的跳转。为什么架构中不直接使用iOS API提供的pushViewControllerpresentViewController等方法呢?这里考虑到两个问题:

    • 页面需要创建后才能够进行跳转,而在创建页面的时候需要进行绑定。如果将页面绑定跟跳转封装在一起将带来较大的便利。
    • 页面间需要传递事件和数据,通常的做法是使用方法传递(正向传递)或者代理模式(反向传递),这样做耦合度较大。需要一种实现页面间传递数据且耦合度较低的机制。

    路由器就是解决以上问题的优雅方案,它与Binder(绑定器)密切结合,并提供页面间数据传递的接口。所以,这篇文章所讲述的架构模式名为MVVMR(传统MVVM+Router)

    架构实现

    下面就是"Show My Code"的时间,以下我会贴出初步实现MVVMR架构的相关代码,对于一些更为细节的实现封装,我会在下一篇文章中谈到。

    协议部分

    Swift作为一门倾于“面向协议”编程范式的语言,编写的时候当然要更好地去发挥其协议的作用。

    Input & Output Protocol

    /// Input Output
    protocol ViewToViewModelInput {
        init(view: MVVMView)
    }
    protocol ViewModelToViewOutput {
        init(viewModel: MVVMViewModel)
    }
    

    上面就是InputOutput的协议定义,从名字上可以很清晰地看出它们的作用,一个是从View传递到ViewModel,而另一个则是反过来传递。它们的构造都需要自身的发出者。

    Provider Protocol

    /// Provider
    protocol ViewToViewModelInputProvider {
        var inputType: ViewToViewModelInput.Type? { get }
        func provideInput() -> ViewToViewModelInput?
    }
    
    extension ViewToViewModelInputProvider where Self: MVVMView {
        func provideInput() -> ViewToViewModelInput? {
            return self.inputType?.init(view: self)
        }
    }
    
    protocol ViewModelToViewOutputProvider {
        var outputType: ViewModelToViewOutput.Type? { get }
        func provideOutput() -> ViewModelToViewOutput?
    }
    
    extension ViewModelToViewOutputProvider where Self: MVVMViewModel {
        func provideOutput() -> ViewModelToViewOutput? {
            return self.outputType?.init(viewModel: self)
        }
    }
    

    Provider(提供者)是针对InputOutput的构建而设计的,意为InputOutput的提供者。每个提供者里具有一个元类类型的属性以及一个提供方法,而在分类中,提供方法已经帮我们去实现了。所以在实现提供者协议的时候,我们只需提供相应的InputOutput类型即可。

    View 和 ViewModel 实现

    View以及ViewModel的实现,我是拟好了两个抽象类:MVVMViewMVVMViewModel

    // MARK: - View & ViewModel
    class MVVMView: UIViewController, ViewToViewModelInputProvider {
        private let viewModelType: MVVMViewModel.Type?
        private(set) var router: Router!
        var viewModel: MVVMViewModel?
        var inputType: ViewToViewModelInput.Type? { return nil }
        var receive: Driver<Any>?
        
        required init(_ viewModelType: MVVMViewModel.Type?) {
            self.viewModelType = viewModelType
            super.init(nibName: nil, bundle: nil)
            self.router = Router(from: self)
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            if let viewModelType = self.viewModelType, let input = self.provideInput() {
                self.viewModel = viewModelType.init(input: input)
                if let output = self.viewModel!.provideOutput() {
                    rxDrive(viewModelOutput: output)
                }
            }
        }
        
        func rxDrive(viewModelOutput: ViewModelToViewOutput) { crash("抽象方法,在此进行绑定,此方法必须重写!") }
        func provideCallBack() -> Driver<Any>? { return nil }
        
        let disposeBag = DisposeBag()
    }
    
    class MVVMViewModel: NSObject, ViewModelToViewOutputProvider {
        let input: ViewToViewModelInput
        var outputType: ViewModelToViewOutput.Type? { return nil }
        required init(input: ViewToViewModelInput) {
            self.input = input
        }
    }
    

    我们先看这两个抽象类的继承以及协议关系:MVVMView继承的是UIViewController,所以这时候UIViewController作为View层的一员,只负责视图的显示相关,并不会接触到业务逻辑的处理。MVVMViewModel则简单地继承NSObject。这两个抽象类都是实现了Provider协议,担任了InputOutput的提供职责。在提供者协议实现元类型的属性时,我返回的是nil,若继承抽象类的子类没有重写此属性,则告诉框架自己并没有提供了InputOutput,对此框架就取消对它们的绑定。
    我们可以看到,MVVMViewMVVMViewModel进行了强引用,我们只需持有MVVMView实例,就能依旧维持MVVMViewModel的存在;并且,MVVMViewModelMVVMView中的访问修饰为private,因此所有继承MVVMView都抽象类都无法访问此属性,做到了两者的高度解耦。
    这里在详细看回MVVMView,其具有router(路由器)属性,通过路由器,我们可以进行页面的跳转;另外有几个抽象的属性和方法:

    • receive : 用于页面的通信,与Router(路由器密切相关),当此页面收到上一个页面所传递进来的事件数据时,receive就会赋入这些信息。在设计上,receive的赋值紧接在MVVMView的初始化之后,所以,我们不可以在重写的初始化方法中获取receive的值。
    • rxDrive(viewModelOutput:) : 这个方法就是用于将从ViewModel层出来的事件数据流驱动整个页面的显示,这方法中,我们可以通过传进来的参数Ouput驱动视图的刷新显示、进行页面的跳转。这个方法也是ViewModel层向View层传递信息的唯一出口。
    • provideCallBack() : 此方法是用于页面跳转中的反向数据传递,即将数据从本页面传到上一个页面,因为我们使用的是响应式的RxSwift框架,所以数据的反向传递就不需要使用到闭包或代理模式。在后面的Router实现中会说到它的机制。
    • disposeBag : 用于RxSwift资源的回收。

    MVVMViewviewDidLoad()方法中,我们进行MVVMViewMVVMViewModel的关联,如果MVVMViewMVVMViewModel没有提供InputOutput,则表明此时ViewViewModel层没有通信,所以也就不会调用rxDrive方法了。

    UnitCase

    // MARK: - Unit
    struct MVVMUnit {
        let viewType: MVVMView.Type
        let viewModelType: MVVMViewModel.Type
    }
    
    extension MVVMUnit: ExpressibleByArrayLiteral {
        typealias Element = AnyClass
        init(arrayLiteral elements: Element...) {
            guard elements.count == 2 else { crash("单元初始化参数长度错误") }
            guard let viewType = elements[0] as? MVVMView.Type else { crash("单元初始化参数类型错误") }
            guard let viewModelType = elements[1] as? MVVMViewModel.Type else { crash("单元初始化参数类型错误") }
            self.viewType = viewType
            self.viewModelType = viewModelType
        }
    }
    
    struct MVVMUnitCase: RawRepresentable {
        typealias RawValue = MVVMUnit
        
        let rawValue: MVVMUnit
        
        init(rawValue: RawValue) {
            self.rawValue = rawValue
        }
    }
    

    上面的代码使用到了一个函数crash(_ message:),为断言函数,这里就不需要给出具体实现了。
    我们先来看MVVMUnit,它具有两个元类型的属性,分别代表一个页面中的MVVMViewMVVMViewModel类型,通过这种关系,绑定器就能正确地按照一一对应关系绑定MVVMViewMVVMViewModelMVVMUnit还实现了ExpressibleByArrayLiteral,我们可以直接简便地通过数组字面量来初始化MVVMUnit
    MVVMUnitCase则是对MVVMUnit的再一次封装,其实现了RawRepresentable协议,这样我们就能像使用枚举一样通过点.语法来创建它。
    使用的话我这里举个例子,加入现在我们的项目中需要用到两个页面,一个是主页面"main",一个是登录页面"login",它们都有对应的MVVMViewMVVMViewModelMainMVVMView、MainMVVMViewModelLoginMVVMView、LoginMVVMViewModel,我们则需要在MVVMUnitCase中进行添加:

    extension MVVMUnitCase {
        static let main = MVVMUnitCase(rawValue: [MainMVVMView.self, MainMVVMViewModel.self])
        static let simpleInfo = MVVMUnitCase(rawValue: [LoginMVVMView.self, LoginMVVMViewModel.self])
    }
    

    Binder

    //  Binder
    struct MVVMBinder {
        /// 根据标识符获取视图,会在背后做视图与视图模型的绑定
        ///
        /// - Parameter identifier: 标识符
        /// - Returns: 返回已经绑定好了的视图
        static func obtainBindedView(_ unitCase: MVVMUnitCase) -> MVVMView {
            let unit = unitCase.rawValue
            let viewType = unit.viewType
            let viewModelType = unit.viewModelType
            let view = viewType.init(viewModelType)
            return view
        }
    }
    

    绑定器做的事情是对MVVMViewMVVMViewModel的绑定,它具有一个静态方法obtainBindedView(_ unitCase:)在这个方法中我们需要传入一个MVVMUnitCase的实例,然后绑定器会帮我们创建MVVMViewMVVMViewModel实例并进行绑定,最后将返回MVVMView的实例,我们拿到这个实例就能进行页面的跳转。

    Router

    // MARK: - Router
    enum RouterType {
        case push(MVVMUnitCase)
        case present(MVVMUnitCase)
        case root(MVVMUnitCase)
        case back
    }
    
    struct Router {
        let from: MVVMView
        init(from: MVVMView) {
            self.from = from
        }
        func route(_ type: RouterType, send: Driver<Any>? = nil) -> Driver<Any>? {
            switch type {
            case let .push(unitCase):
                let view = MVVMBinder.obtainBindedView(unitCase)
                view.receive = send
                from.navigationController?.pushViewController(view, animated: true)
                return view.provideCallBack()
            case let .present(unitCase):
                let view = MVVMBinder.obtainBindedView(unitCase)
                view.receive = send
                from.present(view, animated: true, completion: nil)
                return view.provideCallBack()
            case let .root(unitCase):
                let view = MVVMBinder.obtainBindedView(unitCase)
                view.receive = send
                UIApplication.shared.keyWindow?.rootViewController = view
                return view.provideCallBack()
            case .back:
                if from.presentationController != nil {
                    from.dismiss(animated: true, completion: nil)
                } else {
                    _ = from.navigationController?.popViewController(animated: true)
                }
                return nil
            }
        }
    }
    
    extension MVVMView {
        func route(_ type: RouterType, send: Driver<Any>? = nil) -> Driver<Any>? {
            return self.router.route(type, send: send)
        }
    }
    

    常见的页面跳转为导航控制器的PushPop,模态的PresentDismissUIWindowrootViewController(根视图控制器切换),我们把这些跳转作为一个枚举设计了RouterType,其中back则代表PopDismiss,在使用RouterType时,我们将目标的MVVMUnitCase作为枚举的关联值传入。
    对于路由器Router,我们需要使用一个MVVMView来初始化它,代表跳转是从这个MVVMView开始的。调用路由器中的route(_ type: , send:)方法就能进行页面的跳转,其中,参数send就是要传递到下一个页面的事件数据,而方法的返回值则为下一个页面反向传递过来的事件数据,通过MVVMView的抽象方法provideCallBack()
    我也为MVVMView创建了一个扩展,在扩展中我们可以直接调用自身路由器的路由方法。
    这里贴出个路由器的使用例子:

    _ = view.route(.push(.login), send: Driver.just(userToken))
    

    因为MVVMUnitCase实现了RawRepresentable协议,所以我们可以直接通过点语法来取得登录的Unit: .login

    架构使用

    到此,整套架构的实现就基本完成了,下面我们来结合RxSwift来构建一个使用此套架构的Demo:

    View 层

    class DemoMVVMView: MVVMView {
        override func viewDidLoad() {
            super.viewDidLoad()
            self.view.addSubview(self.mButton)
        }
        
        fileprivate lazy var mButton: UIButton = {
            $0.frame = self.view.bounds
            $0.setTitle("点击", for: .normal)
            return $0
        }(UIButton())
        
        override var inputType: ViewToViewModelInput.Type? { return DemoInput.self }
        
        override func rxDrive(viewModelOutput: ViewModelToViewOutput) {
            let output = viewModelOutput as! DemoOutput
            output.color.drive(self.rx.updateButtonBackgroundColor).addDisposableTo(self.disposeBag)
            output.title.drive(self.rx.updateButtonTitle).addDisposableTo(self.disposeBag)
        }
    }
    
    // MARK: - Reactive
    extension Reactive where Base: DemoMVVMView {
        var updateButtonTitle: AnyObserver<String> {
            return UIBindingObserver<Base, String>(UIElement: base) { view, newTitle in
                view.mButton.setTitle(newTitle, for: .normal)
            }.asObserver()
        }
        
        var updateButtonBackgroundColor: AnyObserver<UIColor> {
            return UIBindingObserver<Base, String>(UIElement: base) { view, newColor in
                view.mButton.backgroundColor = newColor
                }.asObserver()
        }
    }
    
    // MARK: - Input
    struct DemoInput: ViewToViewModelInput {
        let refresh: Driver<()>
        init(view: MVVMView) {
            let view = view as! DemoMVVMView
            self.refresh = view.mButton.rx.tap.asDriver()
        }
    }
    

    可以看到,我们在DemoMVVMView中添加了一个按钮,按钮的点击事件作为从View层传入ViewModel层的事件数据流,所以在DemoInput中我们定义了按钮点击的刷新流。
    在属于DemoMVVMViewReactive分类中,里面的观察者代表DemoMVVMView接收到ViewModel层传来的事件数据流时进行的驱动操作,为更新按钮的标题和切换按钮的背景色。
    我们重写inputType属性以及rxDrive方法,在inputType属性中返回DemoInput类型,在rxDrive方法中将ViewModel传过来的Output驱动视图的刷新。

    ViewModel 层

    // MARK: - ViewModel
    class DemoMVVMViewModel: MVVMViewModel {
        override var outputType: ViewModelToViewOutput.Type? { DemoOutput.self }
    }
    
    struct DemoOutput: ViewModelToViewOutput {
        let color: Driver<UIColor>
        let title: Driver<String>
        init(viewModel: MVVMViewModel) {
            let viewModel = viewModel as! DemoMVVMViewModel
            let input = viewModel.input as! DemoInput
            self.color = input.refresh.map { _ in UIColor.orange }
            self.title = input.refresh.map { _ in "数据已刷新" }
        }
    }
    

    ViewModel层相对较为简单,因为现在还未涉及网络请求操作或数据库操作,也没有进行某些业务逻辑的判断处理,所以DemoMVVMViewModel中只有一个重写的OutputType属性。在实际开发中,MVVMViewModel中会持有某些临时的状态变量,或网络、本地数据库框架实例。
    对于流的转换,发生在Output的初始化方法中,可能有人会疑惑: “流的转换不是发生在MVVMViewModel中的吗,为什么会在Output的初始化方法中?” 在前面我说到,流的转换是在ViewModel层发生的,而ViewModel层是包含OutputMVVMViewModel的,并不是说MVVMViewModel名字的关系所以MVVMViewModel就代表整个ViewModel层。在此架构中,MVVMViewModel主要是用于持有某些状态变量、某些服务模块和提供Output类型的。

    MVVMUnitCase

    不要忘记在UnitCase中对刚构建好的MVVM进行配置:

    extension MVVMUnitCase {
        static let demo = MVVMUnitCase(rawValue: [DemoMVVMView.self, DemoMVVMViewModel.self])
    }
    

    在项目开发中,我们就可以在任意MVVMView中进行向"Demo"页面的跳转了:

    _ = self.route(.push(.demo))
    

    上面展示的架构使用Demo较为简单,在下一篇文章中,我会结合封装完成后的网络请求框架,对MVVMR架构进行一此较为大型的实战。

    总结

    这篇文章详细说明了我在项目中搭建出来的MVVMR架构其思想、基本原理以及初步实现,为基于RxSwift的MVVMR架构系列文章的第一篇。若大家发现文章中的内容存在问题或者有更好的建议,欢迎向我反馈!
    在下一篇文章中,我会对架构中的某些细节进行实现与封装,如基于Moya + Argo的网络框架封装,并在后面使用架构进行实战使用。

    相关文章

      网友评论

      • 莫寂岚:crash(_ message:) 关于这个函数,是先判断是否有子类实现了这个方法,然后再给定断言吗?如何判端子类是否对其有实现呢?
      • 莫寂岚:大佬辛苦了,什么时候有下一篇文章呢?
      • 慌莫染:大佬辛苦了,大佬可不可以发一下demo,qq1101247794 谢谢Thanks♪(・ω・)ノ
      • 走停2015_iOS开发:这篇文章干活太多 但是有点代码连接不起来 可能是我理解能力出现了问题,不过想要表达的意思是很明了的
      • 走停2015_iOS开发:看了半天 思路都懂 就是代码有点闷
      • b2c5a1b6c897:作者可不可以发一下demo,qq776408559
      • Benot_5ae3:期待下一篇!

        <Base, String> 应该是 <Base, UIColor>

        var updateButtonBackgroundColor: AnyObserver<UIColor> {
        return UIBindingObserver<Base, String>(UIElement: base) { view, newColor in
        view.mButton.backgroundColor = newColor
        }.asObserver()
        }
      • 414362c4894a:之前看了你这篇,感觉input output这种思路分割不错,昨天看到ReactorKit这个百多行的库。感觉里面的Action就是你这儿的input,Status就是output。mutaion操作就是你的vm。有兴趣的可以看看RxTodo这个demo
      • 落入粪池的凤凰:焦灼的等待你的下一篇文章
      • 伟欣_1d53:怎么用起了简书,之前不是自己的那啥吗
        Tangentw:@_八阿哥 哈,是在github.io上搭的,地址是tangent.pink,文章质量也不是很好:grin:
        _八阿哥:@TangentW :heart_eyes: 之前用哪个博客啊
        Tangentw:@伟欣_1d53 现在那边也有放的,不过简书这边可以交流
      • da27c260cc85:Router的话,如果是小项目,是不就没有必要了
        Tangentw:@ArthurChi 哈,看心情吧,用了也相对便捷。
      • 07d93406ec39:一看就知道滿滿乾貨呀!

      本文标题:基于RxSwift的MVVMR架构(一)思想、基本原理及初步实现

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