本文主要是RxSwift Demo的體驗
RxSwift 介紹
-
RxSwift = ReactiveX + Swift
- ReactiveX(簡稱 Rx):是一個可以幫助我們簡化異步編程的框架
- Swift:表示是Rx的swift版本
-
RxSwift 作為一個響應(yīng)式編程框架,有以下兩點好處:
-
聚合邏輯:即通過RxSwift提供的各種操作符實現(xiàn)邏輯的聚合 -
響應(yīng)式編程:即將所有事件都描述成一個可監(jiān)聽的序列,并提供大量功能各異的操作符,通過聲明式的語句來完成數(shù)據(jù)的獲取,轉(zhuǎn)換,結(jié)合及綁定
-
為什么要使用RxSwift?
-
復(fù)合- Rx 就是復(fù)合的代名詞 -
復(fù)用- 因為它易復(fù)合 -
清晰- 因為聲明都是不可變更的 -
易用- 因為它抽象了異步編程,使我們統(tǒng)一了代碼風(fēng)格 -
穩(wěn)定- 因為 Rx 是完全通過單元測試的 -
高大上- 代碼檔次比原生高很多
如果對于響應(yīng)式編程還不了解的同學(xué),可以先閱讀# iOS 底層原理37:鏈式編程 中對響應(yīng)式編程的介紹
初體驗
通過 Cocoapods 導(dǎo)入
pod 'RxSwift', '6.5.0'
pod 'RxCocoa', '6.5.0'
RxSwift 的使用步驟分為以下幾步:
- 創(chuàng)建序列(
萬物皆可 rx) - 訂閱信號
- 發(fā)送信號
- 輸出結(jié)果
主要從以下幾方面來體驗
- KVO
- Target-Action:UIbutton
- 代理:delegate
- 手勢
- 通知:Notification
- timer
- 網(wǎng)絡(luò)請求:URLSession
- 多個任務(wù)間有依賴關(guān)系
- 等待多個并發(fā)任務(wù)完成后處理結(jié)果
KVO
在Swift中實現(xiàn) OC 的KVO
- 傳統(tǒng)實現(xiàn)
KVO 三部曲
- 監(jiān)聽
- 響應(yīng)
- 銷毀
class Person: NSObject {
//由于swift是靜態(tài)語言,而KVO是運行時發(fā)生的,所以需要加 dynamic 關(guān)鍵字
@objc dynamic var name: String = "CJL"
}
//1-設(shè)置監(jiān)聽
func setupKVO(){
self.person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
}
//2-回調(diào)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("響應(yīng)")
print(change as Any)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("來了")
person.name = "\(person.name) 6"
print(person.name)
}
//3-移除監(jiān)聽
deinit{
self.removeObserver(self, forKeyPath: "name", context: nil)
}
- Rx實現(xiàn)
func setupKVO(){
self.person.rx.observeWeakly(String.self, "name")
.subscribe { value in
print(value as Any)
}
.disposed(by: disposeBag)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("來了")
person.name = "\(person.name) 6"
}
Target-Action
這里以
UIButton點擊事件為例
- 傳統(tǒng)實現(xiàn)
var button: UIButton = {
let btn = UIButton(type: .custom)
btn.frame = CGRect(x: 100, y: 100, width: 200, height: 100)
btn.setTitle("按鈕點擊", for: .normal)
btn.backgroundColor = UIColor.lightGray
return btn
}()
func setupButton(){
button.addTarget(self, action: #selector(didClickButton), for: .touchUpInside)
}
@objc func didClickButton(){
print("點擊按鈕")
}
- Rx實現(xiàn)
func setupButton(){
button.rx.controlEvent(.touchUpInside)
.subscribe { _ in
print("點擊事件")
}
.disposed(by: disposeBag)
}
代理
這里以
UITextFiledDelegate代理方法為例
- 傳統(tǒng)實現(xiàn)
func textFieldDidChangeSelection(_ textField: UITextField) {
print(textField.text)
}
- Rx實現(xiàn)
func setupTextField(){
textFiled.rx.text.orEmpty.changed
.subscribe { text in
print(text)
}
.disposed(by: disposeBag)
}
此時還可以將 TextField 跟 button進行綁定,將textfiled輸入的文本作為 button的標題
func setupTextField(){
//textFiled跟button綁定,輸入的內(nèi)容作為button的title
textFiled.rx.text
.bind(to: button.rx.title())
.disposed(by: disposeBag)
}
手勢
這里以
UITapGestureRecognizer點擊手勢為例
- 傳統(tǒng)實現(xiàn)
func setupGestureRecongizer(){
let tap = UITapGestureRecognizer()
self.view.addGestureRecognizer(tap)
self.view.isUserInteractionEnabled = true
tap.addTarget(self, action: #selector(tapAction(_:)))
}
@objc func tapAction(_ tap: UITapGestureRecognizer){
print(tap.view)
}
- Rx實現(xiàn)
func setupGestureRecongizer(){
let tap = UITapGestureRecognizer()
self.view.addGestureRecognizer(tap)
self.view.isUserInteractionEnabled = true
tap.rx.event
.subscribe { tap in
print(tap.view)
}
.disposed(by: disposeBag)
}
通知
這里以
keyboardWillShowNotification(鍵盤彈起通知)為例
- 傳統(tǒng)實現(xiàn)
func setupNotification(){
// 監(jiān)聽鍵盤彈出通知
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name:UIResponder.keyboardWillShowNotification,object: nil)
}
@objc func keyboardWillShow(_ noti: Notification){
print("鍵盤彈起")
}
- Rx實現(xiàn)
func setupNotification(){
// 監(jiān)聽鍵盤彈出通知
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe { noti in
print("鍵盤彈起")
}
.disposed(by: disposeBag)
}
timer
實現(xiàn)計時器功能
- 傳統(tǒng)實現(xiàn)
5s倒計時
func setupTimer(){
var countDownNum = 5
let countdownTimer = Timer(timeInterval: 1.0, repeats: true) { timer in
if countDownNum == 0 {
// 銷毀計時器
timer.invalidate()
// countDownNum = 5
print(">>> Timer has Stopped!")
} else {
print(">>> Countdown Number: \(countDownNum)")
countDownNum -= 1
}
}
// 設(shè)置寬容度
countdownTimer.tolerance = 0.2
// 添加到當(dāng)前 RunLoop,mode為默認。
RunLoop.current.add(countdownTimer, forMode: .default)
// 開始計時
countdownTimer.fire()
}
- Rx實現(xiàn)
正向計時
注:RxSwift 中的 timer并不是我們常見的幾種timer定義的,而是自定義的,即不斷創(chuàng)建序列,發(fā)送信號來實現(xiàn)的定時器效果,這個后面篇章會詳細講解
func setupTimer(){
var timer: Observable<Int> = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
timer.subscribe { num in
print(num)
}
.disposed(by: disposeBag)
}
網(wǎng)絡(luò)請求
- 傳統(tǒng)實現(xiàn)
func setupNetwork() {
let url = URL(string: "https://www.baidu.com")
URLSession.shared.dataTask(with: url!) { data, response, error in
print(String.init(data: data!, encoding: .utf8)!)
}.resume()
}
- Rx實現(xiàn)
func setupNetwork() {
let url = URL(string: "https://www.baidu.com")
URLSession.shared.rx.response(request: URLRequest(url: url))
.subscribe { response, data in
print(response)
}
.disposed(by: disposeBag)
}
多個任務(wù)間有依賴關(guān)系
通過用戶名密碼取得 Token 然后通過 Token 取得用戶信息,
- 傳統(tǒng)實現(xiàn)
// 用回調(diào)的方式封裝接口
enum API {
/// 通過用戶名密碼取得一個 token
static func token(username: String, password: String,
success: (String) -> Void,
failure: (Error) -> Void) { }
/// 通過 token 取得用戶信息
static func userinfo(token: String,
success: (String) -> Void,
failure: (Error) -> Void) { }
}
func setupTaskDependency(){
/// 通過用戶名和密碼獲取用戶信息
API.token(username: "111", password: "222") { token in
API.userinfo(token: token) { userinfo in
print("獲取用戶信息成功: \(userinfo)")
} failure: { error in
print("獲取用戶信息失敗: \(error)")
}
} failure: { error in
print("獲取用戶信息失敗: \(error)")
}
}
- Rx實現(xiàn)
避免了回調(diào)地獄,使得代碼易讀、已維護
/// 用 Rx 封裝接口
enum API {
/// 通過用戶名密碼取得一個 token
static func token(username: String, password: String) -> Observable<String> {
return Observable<String>.create{ observer in
observer.onNext("1")
return Disposables.create()
}
}
/// 通過 token 取得用戶信息
static func userInfo(token: String) -> Observable<String> {
return Observable<String>.create{ observer in
observer.onNext("2")
return Disposables.create()
}
}
}
func setupTaskDependency(){
API.token(username: "111", password: "222")
.flatMapLatest(API.userInfo)
.subscribe(onNext: { userInfo in
print("獲取用戶信息成功: \(userInfo)")
}, onError: { error in
print("獲取用戶信息失敗: \(error)")
})
.disposed(by: disposeBag)
}
等待多個并發(fā)任務(wù)完成后處理結(jié)果
需要將兩個網(wǎng)絡(luò)請求合并成一個
- 傳統(tǒng)實現(xiàn)
let queue1:DispatchQueue = DispatchQueue.init(label: "textQueue1")
let queue2:DispatchQueue = DispatchQueue.init(label: "textQueue2")
// 隊列queue加入隊列組
queue1.async (group: self.gropQueue){
/*任務(wù)1*/
print("獲取老師信息接口")
}
queue2.async (group: self.gropQueue){
/*任務(wù)2*/
print("獲取老師評論接口")
}
/*2個異步任務(wù)完成后執(zhí)行界面處理操作*/
self.gropQueue.notify(queue: DispatchQueue.main) {
/*界面處理*/
print("統(tǒng)一處理")
}
- Rx實現(xiàn)
用幾行代碼實現(xiàn)復(fù)雜的異步操作
/// 用 Rx 封裝接口
enum API2 {
/// 取得老師的詳細信息
static func teacher(teacherId: Int) -> Observable<String> {
return Observable<String>.create{ observer in
observer.onNext("取得老師的詳細信息")
return Disposables.create()
}
}
/// 取得老師的評論
static func teacherComments(teacherId: Int) -> Observable<[String]> {
return Observable<[String]>.create{ observer in
observer.onNext(["取得老師的評論"])
return Disposables.create()
}
}
}
func setupConcurrentTasks(){
/// 同時取得老師信息和老師評論
Observable.zip(
API2.teacher(teacherId: 110),
API2.teacherComments(teacherId: 130)
).subscribe(onNext: { (teacher, comments) in
print("獲取老師信息成功: \(teacher)")
print("獲取老師評論成功: \(comments.count) 條")
}, onError: { error in
print("獲取老師信息或評論失敗: \(error)")
})
.disposed(by: disposeBag)
}
疑問點
從上面的實現(xiàn)看,主要關(guān)注以下方面:
- 其他對象調(diào)用的 rx 到底是什么?為什么都可以調(diào)用 rx?
- timer 的創(chuàng)建為什么跟其他對象有所不同?
- Observable、Observer是什么?
其他對象調(diào)用的 rx 到底是什么?為什么都可以調(diào)用 rx?
- 點擊 rx 進入其源碼實現(xiàn)
/// A type that has reactive extensions.
public protocol ReactiveCompatible {
/// Extended type 關(guān)聯(lián)屬性
associatedtype ReactiveBase
/// Reactive extensions.
static var rx: Reactive<ReactiveBase>.Type { get set }
/// Reactive extensions.
var rx: Reactive<ReactiveBase> { get set }
}
//協(xié)議的擴展
extension ReactiveCompatible {
/// Reactive extensions.
public static var rx: Reactive<Self>.Type {
get { Reactive<Self>.self }
// this enables using Reactive to "mutate" base type
// swiftlint:disable:next unused_setter_value
set { }
}
/// Reactive extensions.
public var rx: Reactive<Self> {
get { Reactive(self) }
// this enables using Reactive to "mutate" base object
// swiftlint:disable:next unused_setter_value
set { }
}
}
import Foundation
// NSObject 遵循了 rx 所在的協(xié)議
/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }
- 從源碼可以看出 rx 是 ReactiveCompatible 協(xié)議的屬性,而NSObject 遵循了 ReactiveCompatible,而我們常說
萬物皆對象,對象溯源到底最終都指向 NSObject(即 rx 是 NSObject的擴展),所以這里得出一個結(jié)論:萬物即可 rx
其余問題我們將在下一節(jié)來進行一一探索