最近剛剛把接手的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。完結撒花