
簡單說下在MVVM架構(gòu)下使用RxSwift的思路:
ViewController在這個架構(gòu)中,也是屬于View這個層級。
首先,假設(shè)需要搭建一個UI界面,而且這個頁面需要向API發(fā)送請求,獲取數(shù)據(jù)來展示UI界面。
- View 會把需要的請求參數(shù)以
observable的形式讓 ViewModel 接收到(不管是綁定還是訂閱)- ViewModel 從接收到的
observable拿到所需要的數(shù)據(jù),調(diào)用 APIManager, 獲取到類型Model的observable,也就是observable<Model>或者observable<[Model]>- ViewModel 把對應(yīng)類型
Model的observable輸出給 View 使用可以看到 ViewModel 在整個過程中,將對應(yīng)的參數(shù),通過網(wǎng)絡(luò)請求以及數(shù)據(jù)轉(zhuǎn)換,輸出 View 需要的 Model 類型數(shù)據(jù)。
以下是Demo
Demo 地址 ?? >>>WangYiNewsRxSwiftDemo

介紹一些用到的第三方庫
target 'WangYiNews' do
use_frameworks!
pod 'RxSwift'
pod 'RxCocoa'
pod 'SnapKit' # 跟Masonry一樣是用來設(shè)置約束的,swift版
pod 'SwiftyJSON' # Json數(shù)據(jù)轉(zhuǎn)換
pod 'Alamofire' # 用于網(wǎng)絡(luò)請求
pod 'Moya/RxSwift' # 用于網(wǎng)絡(luò)請求
pod 'Kingfisher' # SDWebImage swift 版
pod 'RxDataSources', '~> 3.0' # RxSwift中用于設(shè)置UITableView/UICollectionView data sources
end

》》代碼《《
Model 設(shè)計:
很簡單,demo頁面只需要這些,
imgnewextra數(shù)組是用來存儲三圖的情況
import UIKit
struct NewsModel {
var title: String
var imgsrc: String
var replyCount: String
var source: String
var imgnewextra: [Imgnewextra]?
}
struct Imgnewextra {
var imgsrc: String
}
ViewModel 設(shè)計:
API請求只需要一個
offset的參數(shù),用于獲取offset參數(shù)之后10條新聞, 所以input只需要一個Variable, output對于這個頁面來說,只是需要一個model數(shù)組,用于展示新聞列表。
import RxSwift
import RxCocoa
class NewsViewModel: NSObject {
// input
let offset = Variable("")
// output
var newsData: Driver<[NewsSections]> {
return offset.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMap(NewsDataManager.shared.getNews)
.asDriver(onErrorJustReturn: [])
}
}
output這里的
newsData將input拿到offset,通過網(wǎng)絡(luò)請求和數(shù)據(jù)轉(zhuǎn)換變成driver<[NewsSections]>的代碼,是不可能一步到位的,此處只是附上最終調(diào)用APIManager代碼之后的完整代碼。
所以,知道input能拿到什么之后,就可以去設(shè)計APIManager, 也就是網(wǎng)絡(luò)層。
APIManager(網(wǎng)絡(luò)層) 設(shè)計:
網(wǎng)絡(luò)層使用了 Moya, Alamofire, SwiftyJSON 這幾種常用的第三方庫,要明確一點就是API request之后這個 APIManager 到底要輸出什么?在這里也就是
Observable<[NewsSections]>
import RxCocoa
import RxSwift
import Moya
import Alamofire
import SwiftyJSON
class NewsDataManager: NSObject {
static let shared = NewsDataManager()
private let provider = MoyaProvider<NewsMoya>()
func getNews(_ offset: String) -> Observable<[NewsSections]> {
return Observable<[NewsSections]>.create ({ observable in
self.provider.request(.news(offset), callbackQueue: DispatchQueue.main) { response in
switch response {
case let .success(results):
let news = self.parse(results.data)
observable.onNext(news)
observable.onCompleted()
case let .failure(error):
observable.onError(error)
}
}
return Disposables.create()
})
}
func parse(_ data: Any) -> [NewsSections] {
guard let json = JSON(data)["T1348649079062"].array else { return [] }
var news: [NewsModel] = []
json.forEach {
guard !$0.isEmpty else { return }
var imgnewextras: [Imgnewextra] = []
if let imgnewextraJsonArray = $0["imgnewextra"].array {
imgnewextraJsonArray.forEach {
let subItem = Imgnewextra(imgsrc: $0["imgsrc"].string ?? "")
imgnewextras.append(subItem)
}
}
let new = NewsModel(title: $0["title"].string ?? "", imgsrc: $0["imgsrc"].string ?? "", replyCount: $0["replyCount"].string ?? "", source: $0["source"].string ?? "", imgnewextra: imgnewextras)
news.append(new)
}
return [NewsSections(header: "1", items: news)]
}
}
enum NewsMoya {
case news(_ offset: String)
}
extension NewsMoya: TargetType {
var baseURL: URL {
return URL(string: "https://c.m.163.com")!
}
var path: String {
return "/dlist/article/dynamic"
}
var method: HTTPMethod {
return .get
}
var sampleData: Data {
return Data()
}
var task: Task {
switch self {
case let .news(offset):
let parameters = ["from": "T1348649079062", "devId": "H71eTNJGhoHeNbKnjt0%2FX2k6hFppOjLRQVQYN2Jjzkk3BZuTjJ4PDLtGGUMSK%2B55", "version": "54.6", "spever": "false", "net": "wifi", "ts": "\(Date().timeStamp)", "sign": "BWGagUrUhlZUMPTqLxc2PSPJUoVaDp7JSdYzqUAy9WZ48ErR02zJ6%2FKXOnxX046I", "encryption": "1", "canal": "appstore", "offset": offset, "size": "10", "fn": "3"]
return .requestParameters(parameters: parameters, encoding: URLEncoding.default)
}
}
var headers: [String : String]? {
return ["Content-Type": "text/plain"]
}
}
回到View層:
- View能提供給ViewModel一個offset參數(shù),而且這個參數(shù)是會變的,所以也需要把這個參數(shù)簡單包裝成
Observable, bind 在 ViewModel 的 offset 上(請注意此時的ViewModel的offset是作為Observer), 一旦View里面的offset發(fā)生了變化,ViewModel里面的offset就能接收到。- 而在ViewModel里面,又將自身的
offset作為 Observable, 有變化就會去調(diào)用API,從而獲取到Observable<[NewsSections]>,通過newsData傳出去。- 然后在View上又將newsData綁定在TableView的datasource上,展示拿到的數(shù)據(jù)。
也印證了這張圖:

import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var refreshItem: UIBarButtonItem!
private let viewModel = NewsViewModel()
private let offset = Variable("0")
private let disposeBag = DisposeBag()
private var dataSource: RxTableViewSectionedReloadDataSource<NewsSections>!
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
self.offset.asObservable()
.bind(to: viewModel.offset)
.disposed(by: disposeBag)
dataSource = RxTableViewSectionedReloadDataSource<NewsSections>(configureCell: { dataSource, tableView, indexpath, item in
if item.imgnewextra?.isEmpty ?? true,
let cell = tableView.dequeueReusableCell(withIdentifier: "OneImageNewsTableViewCell", for: indexpath) as? OneImageNewsTableViewCell {
cell.setup(item)
return cell
} else if let cell = tableView.dequeueReusableCell(withIdentifier: "ThreeImagesTableViewCell", for: indexpath) as? ThreeImagesTableViewCell {
cell.setup(item)
return cell
}
return UITableViewCell()
})
tableView.rx.setDelegate(self)
.disposed(by: disposeBag)
viewModel.newsData
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
refreshItem.rx.tap.bind {
let offset = Int(self.offset.value) ?? 0
self.offset.value = "\(offset + 10)"
}.disposed(by: disposeBag)
}
private func setupTableView() {
tableView.register(OneImageNewsTableViewCell.self, forCellReuseIdentifier: "OneImageNewsTableViewCell")
tableView.register(ThreeImagesTableViewCell.self, forCellReuseIdentifier: "ThreeImagesTableViewCell")
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let newsSection = dataSource.sectionModels[indexPath.section]
let news = newsSection.items[indexPath.row]
if news.imgnewextra?.isEmpty ?? true {
return 100.0
}
return 180.0
}
}
寫在最后:
RxSwift可以說是鏈式編程的產(chǎn)物,結(jié)合Rxcocoa之后變成了可以運用在MVVM架構(gòu)上比較具有靈活性的框架。
得去掌握基本的概念之后,才能知道為什么Observable和Observer這樣用。總的來說,不考慮嚴謹性地比喻,可以把上面demo的需求看成一個閉環(huán),看成一個生產(chǎn)行為,View就是珠寶客戶,ViewModel是珠寶雕刻的廠家, APIManager是將珠寶礦石初步加工的廠家。
View將offset給到ViewModel, ViewModel 把拿到的offset給到APIManager進行網(wǎng)絡(luò)請求,拿到對應(yīng)的Model結(jié)果,再一層層給到View。就很像 客戶 拿了張照片,告訴 珠寶雕刻的廠家 他要做照片上的玉,珠寶雕刻的廠家 做了張 設(shè)計稿 給到 珠寶礦石初步加工的廠家, 初步加工之后,再給到珠寶雕刻的廠家 進行驗收或者再次加工,再給到客戶驗收。只不過,中間不管那個角色發(fā)出了指令,下面都會立刻執(zhí)行。