简单说下在MVVM架构下使用RxSwift的思路:
ViewController
在这个架构中,也是属于View
这个层级。
首先,假设需要搭建一个UI界面,而且这个页面需要向API发送请求,获取数据来展示UI界面。
- View 会把需要的请求参数以
observable
的形式让 ViewModel 接收到(不管是绑定还是订阅)- ViewModel 从接收到的
observable
拿到所需要的数据,调用 APIManager, 获取到类型Model
的observable
,也就是observable<Model>
或者observable<[Model]>
- ViewModel 把对应类型
Model
的observable
输出给 View 使用可以看到 ViewModel 在整个过程中,将对应的参数,通过网络请求以及数据转换,输出 View 需要的 Model 类型数据。
以下是Demo
Demo 地址 👉 >>>WangYiNewsRxSwiftDemo
介绍一些用到的第三方库
target 'WangYiNews' do
use_frameworks!
pod 'RxSwift'
pod 'RxCocoa'
pod 'SnapKit' # 跟Masonry一样是用来设置约束的,swift版
pod 'SwiftyJSON' # Json数据转换
pod 'Alamofire' # 用于网络请求
pod 'Moya/RxSwift' # 用于网络请求
pod 'Kingfisher' # SDWebImage swift 版
pod 'RxDataSources', '~> 3.0' # RxSwift中用于设置UITableView/UICollectionView data sources
end
》》代码《《
Model 设计:
很简单,demo页面只需要这些,
imgnewextra
数组是用来存储三图的情况
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 设计:
API请求只需要一个
offset
的参数,用于获取offset
参数之后10条新闻, 所以input只需要一个Variable
, output对于这个页面来说,只是需要一个model数组,用于展示新闻列表。
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
,通过网络请求和数据转换变成driver<[NewsSections]>
的代码,是不可能一步到位的,此处只是附上最终调用APIManager
代码之后的完整代码。
所以,知道input能拿到什么之后,就可以去设计APIManager, 也就是网络层。
APIManager(网络层) 设计:
网络层使用了 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参数,而且这个参数是会变的,所以也需要把这个参数简单包装成
Observable
, bind 在 ViewModel 的 offset 上(请注意此时的ViewModel的offset是作为Observer), 一旦View里面的offset发生了变化,ViewModel里面的offset就能接收到。- 而在ViewModel里面,又将自身的
offset
作为 Observable, 有变化就会去调用API,从而获取到Observable<[NewsSections]>
,通过newsData
传出去。- 然后在View上又将newsData绑定在TableView的datasource上,展示拿到的数据。
也印证了这张图:
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可以说是链式编程的产物,结合Rxcocoa之后变成了可以运用在MVVM架构上比较具有灵活性的框架。
得去掌握基本的概念之后,才能知道为什么Observable
和Observer
这样用。总的来说,不考虑严谨性地比喻,可以把上面demo的需求看成一个闭环,看成一个生产行为,View就是珠宝客户,ViewModel是珠宝雕刻的厂家, APIManager是将珠宝矿石初步加工的厂家。
View将offset
给到ViewModel, ViewModel 把拿到的offset
给到APIManager进行网络请求,拿到对应的Model结果,再一层层给到View。就很像 客户 拿了张照片,告诉 珠宝雕刻的厂家 他要做照片上的玉,珠宝雕刻的厂家 做了张 设计稿 给到 珠宝矿石初步加工的厂家, 初步加工之后,再给到珠宝雕刻的厂家 进行验收或者再次加工,再给到客户验收。只不过,中间不管那个角色发出了指令,下面都会立刻执行。