文档章节

iOS - RxSwift 项目实战记录

LinXunFeng
 LinXunFeng
发布于 2017/09/10 02:04
字数 2349
阅读 45
收藏 0
点赞 0
评论 0

ReactiveX

最近刚刚把接手的OC项目搞定,经过深思熟虑后,本人决定下个项目起就使用Swift(学了这么久的Swift还没真正用到实际项目里。。。),而恰巧RxSwift已经出来有一些时间了,语法也基本上稳定,遂只身前来试探试探这RxSwift,接着就做了个小Demo,有兴趣的同学可以瞧一瞧~

Exhibition

结构

.
├── Controller
│   └── LXFViewController.swift		// 主视图控制器
├── Extension
│   └── Response+ObjectMapper.swift	// Response分类,Moya请求完进行Json转模型或模型数组
├── Model
│   └── LXFModel.swift				// 模型
├── Protocol
│   └── LXFViewModelType.swift		// 定义了模型协议
├── Tool
│   ├── LXFNetworkTool.swift		// 封装Moya请求
│   └── LXFProgressHUD.swift		// 封装的HUD
├── View
│   ├── LXFViewCell.swift			// 自定义cell
│   └── LXFViewCell.xib				// cell的xib文件
└── ViewModel
    └── LXFViewModel.swift			// 视图模型

第三方库

RxSwift			// 想玩RxSwift的必备库
RxCocoa 		// 对 UIKit Foundation 进行 Rx 化
NSObject+Rx		// 为我们提供 rx_disposeBag 
Moya/RxSwift 	// 为RxSwift专用提供,对Alamofire进行封装的一个网络请求库
ObjectMapper 	// Json转模型之必备良品
RxDataSources	// 帮助我们优雅的使用tableView的数据源方法
Then 			// 提供快速初始化的语法糖
Kingfisher		// 图片加载库
SnapKit			// 视图约束库
Reusable 		// 帮助我们优雅的使用自定义cell和view,不再出现Optional
MJRefresh 		// 上拉加载、下拉刷新的库
SVProgressHUD 	// 简单易用的HUD

敲黑板

Moya的使用

Moya是基于Alamofire的网络请求库,这里我使用了Moya/Swift,它在Moya的基础上添加了对RxSwift的接口支持。接下来我们来说下Moya的使用

一、创建一个枚举,用来存放请求类型,这里我顺便设置相应的路径,等下统一取出来直接赋值即可

enum LXFNetworkTool {
    enum LXFNetworkCategory: String {
        case all     = "all"
        case android = "Android"
        case ios     = "iOS"
        case welfare = "福利"
    }
    case data(type: LXFNetworkCategory, size:Int, index:Int)
}

二、为这个枚举写一个扩展,并遵循塄 TargetType,这个协议的Moya这个库规定的协议,可以按住Commond键+单击左键进入相应的文件进行查看

extension LXFNetworkTool: TargetType {
    /// baseURL 统一基本的URL
    var baseURL: URL {
        return URL(string: "http://gank.io/api/data/")!
    }
    
    /// path字段会追加至baseURL后面
    var path: String {
        switch self {
        case .data(let type, let size, let index):
            return "\(type.rawValue)/\(size)/\(index)"
        }
    }
    
    /// HTTP的请求方式
    var method: Moya.Method {
        return .get
    }
    
    /// 请求参数(会在请求时进行编码)
    var parameters: [String: Any]? {
        return nil
    }
    
    /// 参数编码方式(这里使用URL的默认方式)
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    /// 这里用于单元测试,不需要的就像我一样随便写写
    var sampleData: Data {
        return "LinXunFeng".data(using: .utf8)!
    }
    
    /// 将要被执行的任务(请求:request 下载:upload 上传:download)
    var task: Task {
        return .request
    }
    
    /// 是否执行Alamofire验证,默认值为false
    var validate: Bool {
        return false
    }
}

三、定义一个全局变量用于整个项目的网络请求

let lxfNetTool = RxMoyaProvider<LXFNetworkTool>()

至此,我们就可以使用这个全局变量来请求数据了

RxDataSources

如果你想用传统的方式也行,不过这就失去了使用RxSwift的意义。好吧,我们接下来说说如何优雅的来实现tableView的数据源。其实RxDataSources官网上已经有很明确的使用说明,不过我还是总结一下整个过程吧。

概念点 RxDataSources是以section来做为数据结构来传输,这点很重要,可能很多同学会比较疑惑这句话吧,我在此举个例子,在传统的数据源实现的方法中有一个numberOfSection,我们在很多情况下只需要一个section,所以这个方法可实现,也可以不实现,默认返回的就是1,这给我们带来的一个迷惑点:【tableView是由row来组成的】,不知道在坐的各位中有没有是这么想的呢??有的话那从今天开始就要认清楚这一点,【tableView其实是由section组成的】,所以在使用RxDataSources的过程中,即使你的setion只有一个,那你也得返回一个section的数组出去!!!

一、自定义Section 在我们自定义的Model中创建一个Section的结构体,并且创建一个扩展,遵循SectionModelType协议,实现相应的协议方法。约定俗成的写法呢请参考如下方式

LXFModel.swift

struct LXFSection {
    // items就是rows
    var items: [Item]
  	// 你也可以这里加你需要的东西,比如 headerView 的 title
}

extension LXFSection: SectionModelType {
  
  	// 重定义 Item 的类型为 LXFModel
    typealias Item = LXFModel
  
    // 实现协议中的方式
    init(original: LXFSection, items: [LXFSection.Item]) {
        self = original
        self.items = items
    }
}

二、在控制器下创建一个数据源属性

以下代码均在 LXFViewController.swift 文件中

// 创建一个数据源属性,类型为自定义的Section类型
let dataSource = RxTableViewSectionedReloadDataSource<LXFSection>()

使用数据源属性绑定我们的cell

// 绑定cell
dataSource.configureCell = { ds, tv, ip, item in
    // 这个地方使用了Reusable这个库,在LXFViewCell中遵守了相应的协议
	// 使其方便转换cell为非可选型的相应的cell类型
    let cell = tv.dequeueReusableCell(for: ip) as LXFViewCell
    cell.picView.kf.setImage(with: URL(string: item.url))
    cell.descLabel.text = "描述: \(item.desc)"
    cell.sourceLabel.text = "来源: \(item.source)"
    return cell
}

三、将sections序列绑定给我们的rows

output.sections.asDriver().drive(tableView.rx.items(dataSource:dataSource)).addDisposableTo(rx_disposeBag)

大功告成,接下来说说section序列的产生

ViewModel的规范

我们知道MVVM思想就是将原本在ViewController的视图显示逻辑、验证逻辑、网络请求等代码存放于ViewModel中,让我们手中的ViewController瘦身。这些逻辑由ViewModel负责,外界不需要关心,外界只需要结果,ViewModel也只需要将结果给到外界,基于此,我们定义了一个协议LXFViewModelType

一、创建一个LXFViewModelType.swift

LXFViewModelType.swift

// associatedtype 关键字 用来声明一个类型的占位符作为协议定义的一部分
protocol LXFViewModelType {
    associatedtype Input
    associatedtype Output
    
    func transform(input: Input) -> Output
}

二、viewModel遵守LXFViewModelType协议

  1. 我们可以为XFViewModelType的Input和Output定义别名,以示区分,如:你这个viewModel的用于请求首页模块相关联的,则可以命名为:HomeInput 和 HomeOutput
  2. 我们可以丰富我们的 Input 和 Output 。可以看到我为Output添加了一个序列,类型为我们自定义的LXFSection数组,在Input里面添加了一个请求类型(即要请求什么数据,比如首页的数据)
  3. 我们通过 transform 方法将input携带的数据进行处理,生成了一个Output

注意: 以下代码为了方便阅读,进行了部分删减

LXFViewModel.swift

extension LXFViewModel: LXFViewModelType {
   // 存放着解析完成的模型数组
   let models = Variable<[LXFModel]>([])
  
    // 为LXFViewModelType的Input和Output定义别名
    typealias Input = LXFInput
    typealias Output = LXFOutput

  	// 丰富我们的Input和Output
    struct LXFInput {
        // 网络请求类型
        let category: LXFNetworkTool.LXFNetworkCategory
        
        init(category: LXFNetworkTool.LXFNetworkCategory) {
            self.category = category
        }
    }

    struct LXFOutput {
        // tableView的sections数据
        let sections: Driver<[LXFSection]>
        
        init(sections: Driver<[LXFSection]>) {
            self.sections = sections
        }
    }
    
    func transform(input: LXFViewModel.LXFInput) -> LXFViewModel.LXFOutput {
        let sections = models.asObservable().map { (models) -> [LXFSection] in
            // 当models的值被改变时会调用,这是Variable的特性
            return [LXFSection(items: models)] // 返回section数组
        }.asDriver(onErrorJustReturn: [])
        
        let output = LXFOutput(sections: sections)
        
      	// 接下来的代码是网络请求,请结合项目查看,不然会不方便阅读和理解
    }
}

接着我们在ViewController中初始化我们的input,通过transform得到output,然后将我们output中的sections序列绑定tableView的items

LXFViewController.swift

// 初始化input
let vmInput = LXFViewModel.LXFInput(category: .welfare)
// 通过transform得到output
let vmOutput = viewModel.transform(input: vmInput)

vmOutput.sections.asDriver().drive(tableView.rx.items(dataSource: dataSource)).addDisposableTo(rx_disposeBag)

RxSwift中使用MJRefresh

一、定义一个枚举LXFRefreshStatus,用于标志当前刷新状态

enum LXFRefreshStatus {
    case none
    case beingHeaderRefresh
    case endHeaderRefresh
    case beingFooterRefresh
    case endFooterRefresh
    case noMoreData
}

二、在LXFOutput添加一个refreshStatus序列,类型为LXFRefreshStatus

// 给外界订阅,告诉外界的tableView当前的刷新状态
let refreshStatus = Variable<LXFRefreshStatus>(.none)

我们在进行网络请求并得到结果之后,修改refreshStatus的value为相应的LXFRefreshStatus项

三、外界订阅output的refreshStatus

外界订阅output的refreshStatus,并且根据接收到的值进行相应的操作

vmOutput.refreshStatus.asObservable().subscribe(onNext: {[weak self] status in
    switch status {
    case .beingHeaderRefresh:
        self?.tableView.mj_header.beginRefreshing()
    case .endHeaderRefresh:
        self?.tableView.mj_header.endRefreshing()
    case .beingFooterRefresh:
        self?.tableView.mj_footer.beginRefreshing()
    case .endFooterRefresh:
        self?.tableView.mj_footer.endRefreshing()
    case .noMoreData:
        self?.tableView.mj_footer.endRefreshingWithNoMoreData()
    default:
        break
    }
}).addDisposableTo(rx_disposeBag)

四、output提供一个requestCommond用于请求数据

PublishSubject 的特点:即可以作为Observable,也可以作为Observer,说白了就是可以发送信号,也可以订阅信号

// 外界通过该属性告诉viewModel加载数据(传入的值是为了标志是否重新加载)
let requestCommond = PublishSubject<Bool>()

在transform中,我们对生成的output的requestCommond进行订阅

output.requestCommond.subscribe(onNext: {[unowned self] isReloadData in
    self.index = isReloadData ? 1 : self.index+1
    lxfNetTool.request(.data(type: input.category, size: 10, index: self.index)).mapArray(LXFModel.self).subscribe({ [weak self] (event) in
        switch event {
        case let .next(modelArr):
            self?.models.value = isReloadData ? modelArr : (self?.models.value ?? []) + modelArr
            LXFProgressHUD.showSuccess("加载成功")
        case let .error(error):
            LXFProgressHUD.showError(error.localizedDescription)
        case .completed:
            output.refreshStatus.value = isReloadData ? .endHeaderRefresh : .endFooterRefresh
        }
    }).addDisposableTo(self.rx_disposeBag)
}).addDisposableTo(rx_disposeBag)

五、在ViewController中初始化刷新控件

为tableView设置刷新控件,并且在创建刷新控件的回调中使用output的requestCommond发射信号

tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: { 
    vmOutput.requestCommond.onNext(true)
})
tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { 
    vmOutput.requestCommond.onNext(false)
})

总结流程:

  1. ViewController已经拿到output,当下拉加载数据的时候,使用output的requestCommond发射信息,告诉viewModel我们要加载数据

  2. viewModel请求数据,在处理完json转模型或模型数组后修改models,当models的值被修改的时候会发信号给sections,sections在ViewController已经绑定到tableView的items了,所以此时tableView的数据会被更新。接着我们根据请求结果,修改output的refreshStatus属性的值

  3. 当output的refreshStatus属性的值改变后,会发射信号,由于外界之前已经订阅了output的refreshStatus,此时就会根据refreshStatus的新值来处理刷新控件的状态

好了,附上RxSwiftDemo。完结撒花

© 著作权归作者所有

共有 人打赏支持
LinXunFeng

LinXunFeng

粉丝 12
博文 2
码字总数 2976
作品 9
深圳
程序员
基于 Moya 和 SwiftyJSON 的快速解析模型工具 - MoyaMapper

MoyaMapper是基于 Moya 和 SwiftyJSON 封装的工具,以 Moya 的 plugin 的方式来实现间接解析,支持RxSwift 。 Usage 一、注入 定义一个继承于ModelableParameterType的类 final class NetPar...

LinXunFeng ⋅ 05/21 ⋅ 0

面试官自述:面向高级开发人员的iOS面试问题

当您准备进行技术性iOS面试时,了解您可能会询问哪些主题以及经验丰富的iOS开发人员期望什么是非常重要的。 这是许多硅谷公司用来衡量iOS候选人资历水平的一系列问题。 这些问题涉及iOS开发的...

菇哒微课 ⋅ 04/26 ⋅ 0

RxSwift—Observable介绍、创建可观察序列

目前正在入门RxSwift,这是我的笔记>>>,按照课时整理,与大家共同进步。 1,Observable Observable 这个类就是 Rx 框架的基础,我们可以称它为可观察序列。它的作用就是可以异步地产生一系列...

manofit ⋅ 06/07 ⋅ 0

打造Moya便捷解析库,提供RxSwift拓展

一、概述 1、相信大家在使用Swift开发时,Moya是首选的网络工具,在模型解析这一块,Swift版模型解析的相关第三方库有很多,本人最习惯用的就是SwiftyJSON。 2、下面会开始讲解整个主要的开发...

LinXunFeng ⋅ 05/22 ⋅ 0

iOS原生混合RN开发最佳实践

做过原生iOS开发或者Android开发的同学们肯定也都了解Hybrid,有一些Hybrid的开发经验,目前我们企业开发中运用最广泛的Hybrid App技术就是原生与H5 hybrid,在早期的时候,可能部分同学也接...

光强 ⋅ 05/16 ⋅ 0

【AR】开始使用Vuforia开发iOS(2)

原 设置iOS开发环境 安装Vuforia iOS SDK 如何安装Vuforia iOS示例 编译并运行Vuforia iOS示例 支持iOS金属 iOS 64位迁移 设置iOS开发环境 适用于iOS的Vuforia引擎目前支持运行iOS 9及更高版...

lichong951 ⋅ 06/11 ⋅ 0

开发微信H5视频秀项目遇到的坑

介绍 手头上正好有个项目,需要做一个微信端H5视频秀的一个项目,想想好像挺简单的,由两个视频组成,播放完第一个视频后点击按钮继而播放第二个视频。好了,结果微信的坑TM的多 问题排查 自...

🚲Allen ⋅ 05/18 ⋅ 0

Dhar/YTTInjectedContentKit

YTTInjectedContentKit iOS壳版本场景下的批量修改类名、属性名、插入混淆代码、修改项目名称的shell脚本 具体的实现和使用方法请参考我的博客文章: iOS使用shell脚本注入混淆内容 iOS使用S...

Dhar ⋅ 05/04 ⋅ 0

iOS高仿QQ侧滑控件、下载框架、动画效果、扫一扫、颜色变化、K线图等源码

iOS精选源码 仿京东"加入购物车"转场动画(http://www.code4app.com/thread-28162-1-1.html) ColorTool(颜色转换)(http://www.code4app.com/thread-29256-1-1.html) Swift 专业版K线(http://w......

sunnyaigd ⋅ 04/17 ⋅ 0

iOS三维菜单、调试工具、封装通讯录、网络框架、多种控件和动画等源码

iOS精选源码 一个调用系统通讯录和获取通讯录所有联系人的封装(http://www.code4app.com/thread-29726-1-1.html) ios scrollview嵌套tableview同向滑动(初级、进阶), 支持OC / Swift(http...

sunnyaigd ⋅ 05/15 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

uWSGI + Django @ Ubuntu

创建 Django App Project 创建后, 可以看到路径下有一个wsgi.py的问题 uWSGI运行 直接命令行运行 利用如下命令, 可直接访问 uwsgi --http :8080 --wsgi-file dj/wsgi.py 配置文件 & 运行 [u...

袁祾 ⋅ 36分钟前 ⋅ 0

JVM堆的理解

在JVM中,我们经常提到的就是堆了,堆确实很重要,其实,除了堆之外,还有几个重要的模块,看下图: 大 多数情况下,我们并不需要关心JVM的底层,但是如果了解它的话,对于我们系统调优是非常...

不羁之后 ⋅ 昨天 ⋅ 0

推荐:并发情况下:Java HashMap 形成死循环的原因

在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障,并且这个事发生了很多次,原因是在Java语言在并发情况下使用HashMap造成Race Condition,从而导致死循环。这个事情我4、5年前也经历...

码代码的小司机 ⋅ 昨天 ⋅ 1

聊聊spring cloud gateway的RetryGatewayFilter

序 本文主要研究一下spring cloud gateway的RetryGatewayFilter GatewayAutoConfiguration spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/config/G......

go4it ⋅ 昨天 ⋅ 0

创建新用户和授予MySQL中的权限教程

导读 MySQL是一个开源数据库管理软件,可帮助用户存储,组织和以后检索数据。 它有多种选项来授予特定用户在表和数据库中的细微的权限 - 本教程将简要介绍一些选项。 如何创建新用户 在MySQL...

问题终结者 ⋅ 昨天 ⋅ 0

android -------- 颜色的半透明效果配置

最近有朋友问我 Android 背景颜色的半透明效果配置,我网上看资料,总结了一下, 开发中也是常常遇到的,所以来写篇博客 常用的颜色值格式有: RGB ARGB RRGGBB AARRGGBB 这4种 透明度 透明度...

切切歆语 ⋅ 昨天 ⋅ 0

CentOS开机启动subversion

建立自启动脚本: vim /etc/init.d/subversion 输入如下内容: #!/bin/bash## subversion startup script for the server## chkconfig: 2345 90 10# description: start the subve......

随风而飘 ⋅ 昨天 ⋅ 0

版本控制工具

CSV , SVN , GIT ,VSS

颖伙虫 ⋅ 昨天 ⋅ 0

【2018.06.19学习笔记】【linux高级知识 13.1-13.3】

13.1 设置更改root密码 13.2 连接mysql 13.3 mysql常用命令

lgsxp ⋅ 昨天 ⋅ 0

LVM

LVM: 硬盘划分分区成物理卷->物理卷组成卷组->卷组划分逻辑分区。 1.磁盘分区: fdisk /dev/sdb 划分几个主分区 输入t更改每个分区类型为8e(LVM) 使用partprobe生成分区的文件:如/dev/sd...

ZHENG-JY ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部