RxSwift + MVVM 初体验

作者: 无神 | 来源:发表于2019-09-29 07:06 被阅读0次

    一、原起

    作为一名iOS开发者,必须跟上时代的潮流,随着swift ABI越来越稳定,使用swift开发iOS APP 的人越来越多。从网上看了很多文章,也从github上下载了很多demo进行代码学习。最近使用RxSwift+MVVM+Moya进行了swift的体验之旅。加入到swift开发的大潮中去。

    二、目录结构

    这个demo的项目结构包括:ViewModelViewModelControllerToolExtension

    ViewModelMVVM架构模式与MVC架构模式最大的区别点。MVVM架构模式把业务逻辑从controller集中到了ViewModel中,方便进行单元测试自动化测试

    ViewModel的业务模型如下:

    ViewModel模型

    viewmodel相当于是一个黑盒子,封装了业务逻辑,进行输入和输出的转换。
    其中ViewModelMVC架构模式下负责的任务相同。controller由于业务逻辑移到了Viewmodel中,它本身担起了中间调用者角色,负责把ViewViewmodel绑定在一起。

    demo的整体目录结构如下:


    groups

    三、使用到的第三方库

    开发一个App最基本的三大要素:网络请求数据解析UI布局,其它的都是这三大要素相关联的,或者更细的功能划分。

    • 网络请求库使用的Moya
    • 数据解析使用的是ObjectMapper
    • UI布局使用的是自动布局框架Snapkit
    • 图片加载和缓存使用的是Kingfisher
    • 刷新组件使用的MJRefresh
    • 网络加载提示使用的是SVProgressHUD

    使用到的三方库的cocoapod目录如下:


    cocoapods

    四、具体实现

    4.1 viewmodel的协议

    viewmodel的实现需要继承NJWViewModelType这个协议,需要实现输入->输出这个方法。这个算是viewmodel的一个基本范式吧。

    protocol NJWViewModelType {
        associatedtype Input
        associatedtype Output
        
        func transform(input: Input) -> Output
    }
    

    4.2 viewmodel的具体实现

    这里包括了输入输出的具体实现,与及func transform(input: NJWViewModel.NJWInput) -> NJWViewModel.NJWOutput这个输入转输出方法具体的实现逻辑。具体代码如下:

    class NJWViewModel: NSObject {
        
        let models = Variable<[GirlModel]>([])
        var index: Int = 0
    }
    
    extension NJWViewModel: NJWViewModelType{
        
        typealias Input = NJWInput
        typealias Output = NJWOutput
        
        struct NJWInput {
            
            var category = BehaviorRelay<ApiManager.GirlCategory>(value: .GirlCategoryAll)
            init(category: BehaviorRelay<ApiManager.GirlCategory>) {
                self.category = category
            }
        }
        
        struct NJWOutput {
            
            let sections: Driver<[NJWSection]>
            let requestCommand = PublishSubject<Bool>()
            let refreshStatus = Variable<NJWRefreshStatus>(.none)
            
            init(sections: Driver<[NJWSection]>) {
                self.sections = sections
            }
        }
        
        func transform(input: NJWViewModel.NJWInput) -> NJWViewModel.NJWOutput {
            let sections = models.asObservable().map{ (models) -> [NJWSection] in
                return [NJWSection(items: models)]
            }.asDriver(onErrorJustReturn: [])
            
            let output = Output(sections: sections)
            input.category.asObservable().subscribe{
              
                let category = $0.element
                
                output.requestCommand.subscribe(onNext: { [unowned self] isReloadData in
                    self.index = isReloadData ? 0 : self.index + 1
                    NJWNetTool.rx.request(.requestWithcategory(type: category!, index: self.index))
                        .asObservable()
                        .mapArray(GirlModel.self)
                        .subscribe({[weak self] (event) in
                            switch event{
                                
                            case let .next(modelArr):
                                self?.models.value = isReloadData ? modelArr : (self?.models.value ?? []) + modelArr
                                NJWProgressHUD.showSuccess("加载成功")
                            case let .error(error):
                                NJWProgressHUD.showError(error.localizedDescription)
                            case .completed:
                                output.refreshStatus.value = isReloadData ? NJWRefreshStatus.endHeaderRefresh : NJWRefreshStatus.endFooterRefresh
                            }
                        }).disposed(by: self.rx.disposeBag)
                }).disposed(by: self.rx.disposeBag)
                
            }.disposed(by: rx.disposeBag)
            
            return output
        }
    }
    

    4.3 controller中数据绑定的具体实现

    输入输出collectionview进行绑定,建立联系,达到操作UI进行数据刷新的目的。具体的绑定逻辑如下:

    fileprivate func bindView(){
            
            let vmInput = NJWViewModel.NJWInput(category: self.category)
            let vmOutput = viewModel.transform(input: vmInput)
            vmOutput.sections.asDriver().drive(collectionView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
            vmOutput.refreshStatus.asObservable().subscribe(onNext: {[weak self] status in
                switch status {
                case .beingHeaderRefresh:
                    self?.collectionView.mj_header.beginRefreshing()
                case .endHeaderRefresh:
                    self?.collectionView.mj_header.endRefreshing()
                case .beingFooterRefresh:
                    self?.collectionView.mj_footer.beginRefreshing()
                case .endFooterRefresh:
                    self?.collectionView.mj_footer.endRefreshing()
                case .noMoreData:
                    self?.collectionView.mj_footer.endRefreshingWithNoMoreData()
                default:
                    break
                }
            }).disposed(by: rx.disposeBag)
            
    //        Observable.zip(collectionView.rx.itemSelected, collectionView.rx.modelSelected(GirlModel.self)).bind(onNext: {[weak self] indexPath, itemModel in
    //            var phtoUrlArray: Array<String> = []
    //            phtoUrlArray.append(itemModel.image_url)
    //            let photoBrowser: SYPhotoBrowser = SYPhotoBrowser(imageSourceArray: phtoUrlArray, caption: nil, delegate: self)
    ////                photoBrowser.prefersStatusBarHidden = false
    ////            photoBrowser.pageControlStyle = SYPhotoBrowserPageControlStyle
    //            photoBrowser.initialPageIndex = UInt(indexPath.item)
    //            UIApplication.shared.delegate?.window?!.rootViewController?.present(photoBrowser, animated: true)
    //        }).disposed(by: disposeBag)
            collectionView.rx.modelSelected(GirlModel.self).subscribe(onNext:{[weak self] itemModel in
    
                print("current selected model is \(itemModel)")
                let photoBrowser: SYPhotoBrowser = SYPhotoBrowser(imageSourceArray: [itemModel.image_url], caption: nil, delegate: self)
                //                photoBrowser.prefersStatusBarHidden = false
                //            photoBrowser.pageControlStyle = SYPhotoBrowserPageControlStyle
                UIApplication.shared.delegate?.window?!.rootViewController?.present(photoBrowser, animated: true)
    
            }).disposed(by: disposeBag)
            
            collectionView.mj_header = MJRefreshNormalHeader(refreshingBlock: {
                vmOutput.requestCommand.onNext(true)
    //            self.collectionView.reloadData()
            })
            
            collectionView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: {
                vmOutput.requestCommand.onNext(false)
            })
        }
    

    五、效果展示如下

    effect

    六、demo地址

    没有demo的文章不是好文章,demo的传送门

    相关文章

      网友评论

        本文标题:RxSwift + MVVM 初体验

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