最近刚刚把接手的OC项目搞定,通过深思熟虑后,本人决定下个项目起就使用Swift(学了这么久的Swift还没真正用到实际项目里。。。),而恰巧RxSwift已经出来有一些时间了,语法也基本上稳定,遂只身前来试探试探这RxSwift,接着就作了个小Demo,有兴趣的同窗能够瞧一瞧~android
.
├── 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是基于Alamofire的网络请求库,这里我使用了Moya/Swift,它在Moya的基础上添加了对RxSwift的接口支持。接下来咱们来讲下Moya的使用ios
1、建立一个枚举,用来存放请求类型,这里我顺便设置相应的路径,等下统一取出来直接赋值便可git
enum LXFNetworkTool {
enum LXFNetworkCategory: String {
case all = "all"
case android = "Android"
case ios = "iOS"
case welfare = "福利"
}
case data(type: LXFNetworkCategory, size:Int, index:Int)
}
复制代码
2、为这个枚举写一个扩展,并遵循塄 TargetType,这个协议的Moya这个库规定的协议,能够按住Commond键+单击左键进入相应的文件进行查看github
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
}
}
复制代码
3、定义一个全局变量用于整个项目的网络请求json
let lxfNetTool = RxMoyaProvider<LXFNetworkTool>()
复制代码
至此,咱们就可使用这个全局变量来请求数据了swift
若是你想用传统的方式也行,不过这就失去了使用RxSwift的意义。好吧,咱们接下来讲说如何优雅的来实现tableView的数据源。其实RxDataSources官网上已经有很明确的使用说明,不过我仍是总结一下整个过程吧。api
概念点 RxDataSources是以section来作为数据结构来传输,这点很重要,可能不少同窗会比较疑惑这句话吧,我在此举个例子,在传统的数据源实现的方法中有一个numberOfSection,咱们在不少状况下只须要一个section,因此这个方法可实现,也能够不实现,默认返回的就是1,这给咱们带来的一个迷惑点:【tableView是由row来组成的】,不知道在坐的各位中有没有是这么想的呢??有的话那从今天开始就要认清楚这一点,【tableView实际上是由section组成的】,因此在使用RxDataSources的过程当中,即便你的setion只有一个,那你也得返回一个section的数组出去!!!数组
1、自定义Section 在咱们自定义的Model中建立一个Section的结构体,而且建立一个扩展,遵循SectionModelType协议,实现相应的协议方法。约定俗成的写法呢请参考以下方式bash
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
}
}
复制代码
2、在控制器下建立一个数据源属性微信
如下代码均在 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
}
复制代码
3、将sections序列绑定给咱们的rows
output.sections.asDriver().drive(tableView.rx.items(dataSource:dataSource)).addDisposableTo(rx_disposeBag)
复制代码
大功告成,接下来讲说section序列的产生
咱们知道MVVM思想就是将本来在ViewController的视图显示逻辑、验证逻辑、网络请求等代码存放于ViewModel中,让咱们手中的ViewController瘦身。这些逻辑由ViewModel负责,外界不须要关心,外界只须要结果,ViewModel也只须要将结果给到外界,基于此,咱们定义了一个协议LXFViewModelType
1、建立一个LXFViewModelType.swift
LXFViewModelType.swift
// associatedtype 关键字 用来声明一个类型的占位符做为协议定义的一部分
protocol LXFViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
复制代码
2、viewModel遵照LXFViewModelType协议
注意: 如下代码为了方便阅读,进行了部分删减
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)
复制代码
1、定义一个枚举LXFRefreshStatus,用于标志当前刷新状态
enum LXFRefreshStatus {
case none
case beingHeaderRefresh
case endHeaderRefresh
case beingFooterRefresh
case endFooterRefresh
case noMoreData
}
复制代码
2、在LXFOutput添加一个refreshStatus序列,类型为LXFRefreshStatus
// 给外界订阅,告诉外界的tableView当前的刷新状态
let refreshStatus = Variable<LXFRefreshStatus>(.none)
复制代码
咱们在进行网络请求并获得结果以后,修改refreshStatus的value为相应的LXFRefreshStatus项
3、外界订阅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)
复制代码
4、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)
复制代码
5、在ViewController中初始化刷新控件
为tableView设置刷新控件,而且在建立刷新控件的回调中使用output的requestCommond发射信号
tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: {
vmOutput.requestCommond.onNext(true)
})
tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: {
vmOutput.requestCommond.onNext(false)
})
复制代码
总结流程:
ViewController已经拿到output,当下拉加载数据的时候,使用output的requestCommond发射信息,告诉viewModel咱们要加载数据
viewModel请求数据,在处理完json转模型或模型数组后修改models,当models的值被修改的时候会发信号给sections,sections在ViewController已经绑定到tableView的items了,因此此时tableView的数据会被更新。接着咱们根据请求结果,修改output的refreshStatus属性的值
当output的refreshStatus属性的值改变后,会发射信号,因为外界以前已经订阅了output的refreshStatus,此时就会根据refreshStatus的新值来处理刷新控件的状态
好了,附上RxSwiftDemo。完结撒花