RxSwift的使用詳解01

一. RxSwift簡介

  • RxSwift是Swift函數響應式編程的一個開源庫,由Github的ReactiveX組織開發、維護

  • 其他語言像C#, Java 和 JS 也有,Rx.Net、RxJava、rxjs

  • RxSwift的目的是讓數據/事件流和異步任務能夠更方便的序列化處理,能夠使用Swift進行響應式編程

1. RxSwift做了什么

  • RxSwift把我們程序中每一個操作都看成一個事件
  • 比如一個TextField中的文本改變,一個按鈕被點擊,或者一個網絡請求結束等,每一個事件源就可以看成一個管道,也就是sequence
  • 比如TextField,當我們改變里面的文本的時候,這個TextField就會不斷的發出事件,從他的這個sequence中不斷的流出,我們只需要監聽這個sequence,每流出一個事件就做相應的處理。
  • 同理,Button也是一個sequence,每點擊一次就流出一個事件。

2.1 RxSwift的核心思想是 Observable<Element>

  • sequence,Observable表示可監聽或者可觀察,也就是說RxSwift的核心思想是可監聽的序列。

  • 并且,Observable sequence可以接受異步信號,也就是說,信號是可以異步給監聽者的

    • Observable(ObservableType) 和 SequenceType類似
    • ObservableType.subscribe 和 SequenceType.generate類似
    • 由于RxSwift支持異步獲得信號,所以用ObservableType.subscribe,這和indexGenerator.next()類似
  • 其中SequenceType是Swift(2.3以前版本,之后的版本沒有該協議)中的一個協議,比如Swift中的Array就遵循這個協議,通過這個協議,你可以這樣的去操作一個Array

let array = [1,2,3,4,5]
let array2 = array.filter({$0 > 1}).map({$0 * 2})//4 6 8 10
var indexGenerator = array2.generate()
let fisrt = indexGenerator.next() // 4
let seoncd = indexGenerator.next() //6 

2.2 RxSwift中,ObservableType.subscribe的回調(新的信號到來)一共有三

enum Event<Element>  {
    case Next(Element)      // 新的信號到來
    case Error(ErrorType)   // 信號發生錯誤,序列不會再產生信號
    case Completed          // 序列發送信號完成,不會再產生新的信號
}
protocol ObserverType {
    func on(event: Event<Element>) //監聽所有的信號
}

2.3 取消監聽

Observable分為兩種

  • 在有限的時間內會自動結束(Completed/Error),比如一個網絡請求當作一個序列,當網絡請求完成的時候,Observable自動結束,資源會被釋放
  • 信號不會自己結束,最簡單的比如一個Timer,每隔一段時間發送一個新的信號過來,這時候需要手動取消監聽,來釋放相應的資源
  • 比如一個label.rx.text是一個Obserable,通常需要這樣調用addDisposableTo(disposeBag)來讓其在deinit,也就是所有者要釋放的時候,自動取消監聽
class Observable<Element> {
    func subscribe(observer: Observer<Element>) -> Disposable //調用Disposable的方法來取消

}

當然,除了手動釋放,RxSwift提供了一些操作符,比如 takeUntil來根據條件取消

sequence
    .takeUntil(self.rx_deallocated) //當對象要釋放的時候,取消監聽
    .subscribe {
        print($0)
    }

二. RxSwift簡單體驗

  • 首先創建deinit屬性,也就是所有者要釋放的時候,自動取消監聽
fileprivate lazy var bag = DisposeBag()

1. RxSwift監聽按鈕的點擊

  • 傳統方式:
button1.addTarget(self, action: #selector(btn1Click(_:)), for: .touchUpInside)
  • RxSwift方式
button1.rx.tap.subscribe { (event) in
    self.button1.setTitle("按鈕1", for: .normal)
    print("button1")
}.addDisposableTo(bag)
        
button2.rx.tap.subscribe { (event) in
    self.textField2.text = "按鈕2被點擊了"
}.addDisposableTo(bag)

2. RxSwift監聽UITextField的文字改變

  • 傳統做法,設置textField2.delegate = self
  • RxSwift方式

2-1. 用on方法實現

subscribe(<#T##on: (Event<Int>) -> Void##(Event<Int>) -> Void#>)
textField1.rx.text.subscribe { (event: Event<String?>) in
    //將UITextField文字改變的內容顯示在Label中
    self.label1.text = event.element!    
    print(event.element!!)
}.addDisposableTo(bag)
        
textField2.rx.text.subscribe { (event: Event<String?>) in
    print(event.element)//報警告
    //輸出: Optional(Optional("jun"))
}.addDisposableTo(bag)

2-2. 用onNext方法實現

subscribe(on: (Event<Int>) -> Void)
textField1.rx.text.subscribe(onNext: { (str: String?) in
    self.label1.text = str!
}).addDisposableTo(bag)

3. RxSwift改變Label中文字

label1.rx.observe(String.self, "text").subscribe(onNext: { (str: String?) in
    print(str!)
}).addDisposableTo(bag)
        
label2.rx.observe(CGRect.self, "frame").subscribe(onNext: { (rect: CGRect?) in
    print(rect!.width)
}).addDisposableTo(bag)

4. RxSwift監聽UIScrollView的滾動

scrollView.contentSize = CGSize(width: 1000, height: 0)
scrollView.rx.contentOffset
            .subscribe(onNext: { (point : CGPoint) in
                print(point)
            }).addDisposableTo(bag)


三. RxSwift常見操作

  • addDisposableTo(disposeBag)方法是讓其deinit,也就是所有者要釋放的時候,自動取消監聽
//創建bag
let bag = DisposeBag()

1. never

  • never就是創建一個sequence,但是不發出任何事件信號
let neverSqu = Observable<String>.never()
neverSqu.subscribe { (_) in
    //該語句不會執行
    print("This will never be printed")
}.addDisposableTo(bag)
//調用Disposable的方法來取消

2. empty

  • empty就是創建一個空的sequence,只能發出一個complected事件
Observable<Int>.empty().subscribe { (event) in
    //該語句只會執行一次
    //輸出: completed
    print(event)
}.addDisposableTo(bag)

3. just

  • just是創建一個sequence只能發出一種特定的事件,能正常結束
Observable<Int>.just(3).subscribe { (event) in
    print(event)
    /*該語句只會執行兩次,分別輸出
     1) next(3),語句結果
     2) completed, 結束事件
    */
}.addDisposableTo(bag)
        
Observable.just("jun").subscribe { (event) in
    print(event)
    /*該語句只會執行兩次,分別輸出
     1) next(jun),語句結果
     2) completed, 結束事件
    */
}.addDisposableTo(bag)

4.of

  • of是創建一個sequence能發出很多種事件信號
Observable.of("a", "b", "2", "5.3")
    .subscribe(onNext: { print($0) })
    .addDisposableTo(bag)
    //會分別輸出 "a", "b", "2", "5.3"

Observable.of("a", "b", "2", "5.3").subscribe(onNext: { (event) in
    print(event)
    /*該語句只會執行4次,分別輸出
     1) next(a),語句結果
     2) next(b),語句結果
     3) next(2),語句結果
     4) next(3.5),語句結果
     */
}, onError: nil, onCompleted: nil, onDisposed: nil).addDisposableTo(bag)
   //每一個閉包都設置設置了 一個默認值,故可以省略

5. from

  • from就是從數組中創建sequence
Observable.from(["a", "b", "2", "5.3"]).subscribe { (event) in
    print(event)
    /*該語句只會執行5次,分別輸出
     1) next(a),語句結果
     2) next(b),語句結果
     3) next(2),語句結果
     4) next(3.5),語句結果
     5) completed, 結束事件
     */
}.addDisposableTo(bag)

6. create

  • 我們也可以自定義可觀察的sequence,那就是使用create
  • create操作符傳入一個觀察者observer,然后調用observer的onNext,onCompleted和onError方法。返回一個可觀察的obserable序列

1) 自定義方法創建observable的creat

6-1 無參創建creat

fileprivate func myobserable() -> Observable<Any> {
    return Observable.create({ (observal: AnyObserver<Any>) -> Disposable in
        observal.onNext("abc")
        observal.onNext("12")
        observal.onCompleted()
        return Disposables.create()
    })
}

6-2 添加參數創建creat

fileprivate func myJunst(element: String) -> Observable<String> {
    return Observable.create({ (observal: AnyObserver<String>) -> Disposable in
        observal.onNext(element)
        observal.onCompleted()
        return Disposables.create()
    })
}

2) 在函數內調用自定義方法

myobserable().subscribe(onNext: { print($0) }).addDisposableTo(bag)
//該語句只會執行2次,分別輸出"abc", "12"
 
        
myJunst(element: "jun").subscribe(onNext: { print($0) }).addDisposableTo(bag)
//該語句只會執行2次,分別輸出"jun"

7. range(給定范圍, 依次顯示)

  • range就是創建一個sequence,他會發出這個范圍中的從開始到結束的所有事件
  • Observable<Int>,必須指定數據類型
Observable<Int>.range(start: 1, count: 4).subscribe { (event: Event<Int>) in
    print(event)
    /*該語句只會執行5次,分別輸出
     1) next(1),語句結果
     2) next(2),語句結果
     3) next(3),語句結果
     4) next(4),語句結果
     5) completed, 結束事件
     */
}.addDisposableTo(bag)

8. repeatElement(重復執行)

創建一個sequence,發出特定的事件n次

Observable.repeatElement("quanjun")
    .take(3)
    .subscribe(onNext: { print($0) })
    .addDisposableTo(bag)
    //該語句只會執行3次,每次都輸出"quanjun" 

9. generate(類似于for循環)

  • generate是創建一個可觀察sequence,當初始化的條件為true的時候,他就會發出所對應的事件
let generate = Observable.generate(initialState: 0, condition: { $0 < 5 }, iterate: { $0 + 2 })
generate.subscribe({ print($0) }).addDisposableTo(bag)
        
        /*1> 輸出順序:
         next(0)
         next(2)
         next(4)
         completed
         
         2> 以上代碼可以理解為for循環處理邏輯
        for (Int i = 0; i < 5; i++) {
            print(i)
        }
        */

10. error(發出錯誤信號)

  • 創建一個可觀察序列,但不發出任何正常的事件,只發出error事件并結束
 let error = NSError(domain: "錯誤", code: 10, userInfo: nil) as Error
 Observable<Any>.error(error)
            .subscribe({ print($0) })
            .addDisposableTo(bag)
        
//輸出: error(Error Domain=錯誤 Code=10 "(null)")

四. RxSwift中Subjects

  • Subjects是什么?

    • Subjet是observable和Observer之間的橋梁,一個Subject既是一個Obserable也是一個Observer,他既可以發出事件,也可以監聽事件

1. PublishSubject

  • 當你訂閱PublishSubject的時候,你只能接收到訂閱他之后發生的事件。subject.onNext()發出onNext事件,對應的還有onError()和onCompleted()事件
let pSubject = PublishSubject<String>()
pSubject.subscribe { (event: Event<String>) in
        print("2--", event)
    }.addDisposableTo(bag)
print("1--------------")
        
pSubject.onNext("T")
pSubject.onNext("Q")
        
pSubject.subscribe { (event: Event<String>) in
         print("3--", event)
    }.addDisposableTo(bag)
pSubject.onNext("J")
        
    /*輸出順序為:
         1--------------
         2-- next(T)
         2-- next(Q)
         2-- next(J)
         3-- next(J)
     */

2. ReplaySubject

  • 當你訂閱ReplaySubject的時候,你可以接收到訂閱他之后的事件,但也可以接受訂閱他之前發出的事件,接受幾個事件取決與bufferSize的大小
  • createUnbounded()表示接受所有事件
  • create(bufferSize: 4) 表示可接受到的訂閱他之前的事件的個數,但是訂閱他之后的事件一定會觸發

2-1. createUnbounded()表示接受所有事件

let rSubject = ReplaySubject<String>.createUnbounded()
rSubject.onNext("T")
rSubject.onNext("Q")
rSubject.subscribe { (event: Event<String>) in
         print("0--", event)
    }.addDisposableTo(bag)
        
rSubject.onNext("J")

/*輸出順序為:
     0-- next(T)
     0-- next(Q)
     0-- next(J)
  */

2-2. create(bufferSize: 4) 表示可接受到的訂閱他之前的事件的個數

let rSubject1 = ReplaySubject<String>.create(bufferSize: 1)
rSubject1.onNext("T")
rSubject1.onNext("Q")
rSubject1.subscribe { (event: Event<String>) in
            print("4--", event)
        }.addDisposableTo(bag)
        
rSubject1.onNext("J")

/*輸出順序為:
     4-- next(Q)
     4-- next(J)
  */
  

3. BehaviorSubject

  • 當你訂閱了BehaviorSubject,你會接受到訂閱之前的最后一個事件,訂閱之后的事件一定會觸發
let bSubject = BehaviorSubject(value: "G")
bSubject.subscribe { (event: Event<String>) in
        print("5--", event)
    }.addDisposableTo(bag)
        
bSubject.onNext("Y")
bSubject.onNext("Q")
        
bSubject.subscribe { (event: Event<String>) in
        print("6--", event)
    }.addDisposableTo(bag)
        
bSubject.onNext("J")

/*輸出順序為:
         5-- next(G)
         5-- next(Y)
         5-- next(Q)
         6-- next(Q)
         5-- next(J)
         6-- next(J)
 */

4. Variable

Variable是BehaviorSubject一個包裝箱,就像是一個箱子一樣,使用的時候需要調用asObservable()拆箱,里面的value是一個BehaviorSubject,他不會發出error事件,但是會自動發出completed事件。
  • 1> 相當于對BehaviorSubject進行裝箱
  • 2> 如果想將Variable當成Obserable, 讓訂閱者進行訂閱時, 需要調用asObserable拆箱轉成Obserable
  • 3> 如果Variable打算發出事件, 直接修改對象的value即可
  • 4> 當事件結束時,Variable會自動發出completed事件
let variable = Variable("S")
variable.asObservable().subscribe { (event) in
        print("7--", event)
    }.addDisposableTo(bag)
        
variable.value = "D"
variable.value = "Q"
        
variable.asObservable().subscribe { (event) in
        print("8--", event)
    }.addDisposableTo(bag)
        
variable.value = "j"

/*輸出順序為:
         7-- next(S)
         7-- next(D)
         7-- next(Q)
         8-- next(Q)
         7-- next(j)
         8-- next(j)
         7-- completed
         8-- completed
  */

五. RxSwift細節理解

1. 變換操作

1-1. map

  • 通過傳入一個函數閉包把原來的sequence轉變為一個新的sequence的操作
Observable.of(1, 2, 3, 4)
        .map({ $0 * $0 })
        .subscribe(onNext: { print($0) })
        .addDisposableTo(bag)
        
/* 輸出順序為:
    1
    4
    9
    16
 */

1-2. flatMap

  • 將一個sequence轉換為一個sequences,當你接收一個sequence的事件,你還想接收其他sequence發出的事件的話可以使用flatMap,她會將每一個sequence事件進行處理以后,然后再以一個sequence形式發出事件

首先創建一個struct

struct Student {
    var score: Variable<Double>
}

flatMap執行代碼

let stu1 = Student(score: Variable(80))
let stu2 = Student(score: Variable(100))
        
let student = Variable(stu1)
student.asObservable().flatMap { (stu: Student) -> Observable<Double> in
    return stu.score.asObservable()
}.subscribe { (event) in
    print(event)
}.addDisposableTo(bag)
        
student.value = stu2
stu1.score.value = 10
stu2.score.value = 20
        
    /* 輸出順序為:
         next(80.0)
         next(100.0)
         next(10.0)
         next(20.0)
         completed
     */

1-3. flatMapLatest

  • flatMapLatest只會接收最新的value事件,將上例代碼改為flatMapLatest
let stu3 = Student(score: Variable(70))
let stu4 = Student(score: Variable(60))

let student1 = Variable(stu3)
student1.asObservable().flatMapFirst { (stu) -> Observable<Double> in
    return stu.score.asObservable()
}.subscribe { (event: Event<Double>) in
    print(event)
}.addDisposableTo(bag)
student1.value = stu4
stu3.score.value = 10
stu4.score.value = 20
        
    /* 輸出順序為:
         next(70.0)
         next(10.0)
         completed
     */

2. 釋放資源

  • 當監聽一個事件序列的時候,有消息事件來了,我們做某些事情。但是這個事件序列不再發出消息了,我們的監聽也就沒有什么存在價值了,所以我們需要釋放我們這些監聽資源,其實也就是每種編程語言中的內存資源釋放。
  • OC和Swift中也一樣,在你不需要用某些變量的時候,你需要把這些變量所占用的內存空間釋放掉。
  • 釋放某一個監聽的時候我們可以手動調用釋放方法

2-1. dispose

  • 相當于MRC中手動調用release操作
  • 注意: 因為觀察者已經銷毀, 所有后面無法接受事件
let testVariable = Variable("jun")
testVariable.asObservable().subscribe { (event : Event<String>) in
    print(event)
}.dispose()

testVariable.value = "tian"

    /* 輸出順序為:
         next(jun)
         completed
     */

2-2. Dispose Bags

  • 除了上面手動的方法,還有一種是自動的方式
  • 推薦大家使用這種方式,這種方式就好像iOS中的ARC方式似得,會自動去釋放資源。
fileprivate lazy var bag = DisposeBag()

在代碼結尾調用.addDisposableTo(bag)方法

3. UIBindingObserver

  • UIBindingObserver這個東西很有用的,創建我們自己的監聽者,有時候RxCocoa(RxSwiftz中對UIKit的一個擴展庫)給的擴展不夠我們使用
  • 比如一個UITextField有個isEnabled屬性,我想把這個isEnabled變為一個observer,我們可以這樣做:
extension Reactive where Base: UITextField {
    var inputEnabled: UIBindingObserver<Base, Result> {
        return UIBindingObserver(UIElement: base) { textFiled, result in
            textFiled.isEnabled = result.isValid
        }
    }
}

后續會繼續更新相關方面知識,敬請期待

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容

  • 發現 關注 消息 RxSwift入坑解讀-你所需要知道的各種概念 沸沸騰關注 2016.11.27 19:11*字...
    楓葉1234閱讀 2,817評論 0 2
  • 本文章內部分圖片資源來自RayWenderlich.com 本文結合自己的理解來總結介紹一下RxSwift最基本的...
    FKSky閱讀 2,908評論 4 14
  • RxSwift把我們程序中每一個操作都看成一個事件,比如一個TextField中的文本改變,一個按鈕被點擊,或者一...
    水落斜陽閱讀 811評論 0 2
  • 簡叔最近的一期專題讓大家安利一本最愛的書,腦子里第一反應就是《目送》,看過太多遍了!反而最后一篇魂歸比較少看,早已...
    小人參閱讀 391評論 0 2
  • 麻雀鎮之所以稱之為麻雀鎮,并不是因為盛產那種披著灰褐色的外衣,長著靈活的小腦袋,有一雙瑪瑙似的小眼睛,一天到晚嘰嘰...
    云水禪心千古情閱讀 720評論 7 1