我們了解了創(chuàng)建Observable以及訂閱事件的方法。我們關(guān)注回收Observable使用的資源問題。
在繼續(xù)之前,我們要先補充一點小知識。對于一個Observable來說,當(dāng)它向訂閱者發(fā)送.Completed或.Error事件之后,Observable的使命就結(jié)束了,屬于這個Observable的所有資源都會被自動回收。
我們使用的各種創(chuàng)建Observables的方法,它們創(chuàng)建的都是一個有限序列,因此,當(dāng)最后它們向訂閱者發(fā)送了.Completed或.Error事件之后,屬于這些Observables的資源就被回收了。它們再也不會向訂閱者發(fā)送任何消息。

但有時,事件序列在某種程度上是“無限的”。例如,一個計時器,它可以在固定時間間隔不斷的生成事件。對于這樣的“無限序列”,如果要回收它的資源,我們可以在訂閱事件之后,調(diào)用一個叫做dispose()的方法。

調(diào)用dispose()之后,和事件序列相關(guān)的資源就會被回收,于是,observer就再也不會訂閱到任何事件了。
接下來,我們就看一個具體的例子。
準備工作
為了演示Observable被回收的效果,我們在Main.storyboard里添加了一個UITextField作為計數(shù)器。App運行后,它將每半秒鐘更新一次。
然后,我們還添加了一個按鈕,我們希望當(dāng)點擊這個按鈕時,回收定時器序列資源,這樣,上面這個計數(shù)器就不會再更新了。

為此,我們在ViewController.swift中,添加了對應(yīng)的屬性以及IBOutlet:
class ViewController: UIViewController {
// Omit for simplicity...
var interval: Observable<Int>!
@IBOutlet weak var counter: UITextField!
@IBOutlet weak var disposeCounter: UIButton!
// Omit for simplicity...
}
其中,interval表示我們要使用的計時器事件序列,counter表示用于顯示計數(shù)的UITextField,disposeCounter表示界面中的Stop按鈕對象。
至此,我們的初始環(huán)境就準備完了。接下來,我們從添加計時器序列開始。
一個可以發(fā)送無限事件的Observable
在ViewController的viewDidLoad方法里,添加下面的代碼:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.interval =
Observable.interval(0.5, schedule: MainScheduler.instance)
}
Observable.interval會每隔固定的時間段生成一個整數(shù)值,我們可以在這里找到它的詳細說明。它的第一個參數(shù)用于指定發(fā)送事件的時間間隔,第二個參數(shù)用于指定一個scheduler,它和多線程應(yīng)用相關(guān),后面我們會講到這個話題。我們使用MainScheduler.instance表示在App的主線程里創(chuàng)建這個事件序列。
這樣,self.interval就會在被訂閱的時候,每隔0.5秒,向訂閱者從0開始,發(fā)送一個整數(shù)值事件。由于我們要把這個值顯示在UITextField上,我們先使用map把這個序列變成一個Observable<String>序列:
self.interval.map { return String($0) }
然后,我們訂閱這個事件序列,當(dāng)有事件發(fā)生時,設(shè)置UITextField的值:
self.interval.map { return String($0) }
.subscribeNext { str in
self.counter.text = str
}
其中subscribeNext和subscribe的作用是類似的,只不過前者只訂閱序列中的.Next事件,并且,可以直接在closure參數(shù)中,使用.Next的associated value。
接下來,按Command + R編譯執(zhí)行,就能看到UITextField中的值不斷變化了。

dispose 手動回收事件序列資源
如果我們的App一直運行,那么這個計數(shù)器就會一直工作下去。如果我們要停止計數(shù)并且回收計數(shù)器使用的資源怎么辦呢?第一種方式就是調(diào)用dispose()方法。我們分幾步完成這個工作:
首先,給ViewController添加一個Disposable!類型的屬性,顧名思義,Disposable表示某種“用過之后就扔掉的東西”;
var subscription: Disposable!
其次,在訂閱事件的時候,設(shè)置它:
self.subscription = interval.map { return String($0) }
.subscribeNext { str in
self.counter.text = str
}
其中,subscribeNext返回一個Disposable對象,表示一個“可以用完就扔掉的東西”。
最后,處理Stop按鈕的點擊事件。在viewDidLoad方法里,添加下面的代碼:
self.disposeCounter.rx_tap.subscribeNext {
print("Dispose interval")
self.subscription.dispose()
}
這里,rx_tap是RxSwift對UIButton的一個擴展,表示按鈕點擊的事件序列(其實這也是一個無限事件序列)。
我們直接訂閱了這個序列的.Next事件(也就是按鈕被點擊的事件)。當(dāng)用戶點擊按鈕時,我們向控制臺打印了一個字符串,并且,調(diào)用了subscription的dispose方法。
調(diào)用dispose方法可以理解為,再也不需要訂閱了。既然沒人需要訂閱了,interval序列就會被系統(tǒng)回收,再也不會發(fā)送任何事件了。UITextField將也將被固定在一個特定的值上。

但是,這種手工調(diào)用dispose回收Observable的方法更多是用來示意。RxSwift官網(wǎng)也告訴我們直接調(diào)用dispose并不是一個好主意:
Note the you usually do not want to manually call dispose; this is only educational example. Calling dispose manually is usually a bad code smell. There are better ways to dispose subscriptions. We can use DisposeBag
是的,RxSwift提供了一個集中回收Observable的方式,叫做DisposeBag。
disposeBag 自動回收事件序列資源
我們可以把DisposeBag理解為一個裝Disposable的“袋子”。當(dāng)這個“袋子”被銷毀的時候,它就會逐個銷毀其中的Disposable對象。為了演示它的用法:
首先,我們給ViewController再添加一個屬性:
var bag: DisposeBag! = DisposeBag()
這樣我們就有了一個DisposeBag對象。
其次,在viewDidLoad方法里,把訂閱self.interval的返回值,“裝進袋子里”:
self.subscription.addDisposableTo(self.bag)
最后,在Stop按鈕的事件處理函數(shù)里,把原來的self.subscription.dispose()改成:
self.bag = nil
重新Command + R編譯執(zhí)行,然后在UITextField值發(fā)生變化之后,點擊Stop按鈕,就會發(fā)現(xiàn),這和我們之前調(diào)用dispose方法的效果是一樣的。
因此,作為這種“無限事件序列”,最好的回收方法就是定義一個公用的DisposeBag,然后把它們統(tǒng)統(tǒng)裝進去,當(dāng)這個Bag的值為nil時,所有的序列就都被自動銷毀了。
如果我們把訂閱
rx_tap的事件也放到DisposeBag里,甚至按鈕點擊的功能也就不好用了,不信你試試?