RxSwift + MVVM 初体验

6 篇文章 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的传送门

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Jetpack是一个结合MVVM的快速开发框架,它基于MVVM模式并集成了谷歌官方推荐的Jetpack组件库,包括LiveData、ViewModel、Lifecycle和Navigation组件。这个框架使用Kotlin语言,并添加了大量的拓展函数,以简化代码。它还集成了Retrofit网络请求和协程,可以帮助开发者快速开发项目。\[1\] MVVM是一种软件架构模式,它将应用程序分为三个主要部分:模型(Model)、视图(View)和视图模型(ViewModel)。在MVVM中,视图负责显示数据和用户交互,模型负责处理数据和业务逻辑,而视图模型则充当视图和模型之间的中间层,负责管理视图的状态和数据。Jetpack的MVVM模式可以帮助开发者更好地组织和管理代码,提高开发效率和舒适度。\[1\] 使用Jetpack和MVVM可以带来许多好处,例如简化代码、提高开发效率、提供更好的代码结构和可维护性。通过使用Jetpack的组件库,开发者可以更轻松地处理生命周期管理、数据共享和导航等常见任务。而MVVM模式则可以帮助开发者更好地分离关注点,使代码更易于测试和维护。\[2\] 总之,Jetpack和MVVM是一种强大的组合,可以帮助开发者快速开发Android应用程序,并提供更好的代码结构和可维护性。如果你想了解更多关于Jetpack和MVVM的信息,可以参考引用\[1\]中提供的Jetpack框架的介绍。 #### 引用[.reference_title] - *1* *2* [JetpackMvvm](https://blog.csdn.net/u014608640/article/details/124711159)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [大型Android项目架构:基于组件化+模块化+Kotlin+协程+Flow+Retrofit+Jetpack+MVVM架构实现WanAndroid...](https://blog.csdn.net/m0_37796683/article/details/130277908)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值