理解Disposable & DisposeBag

我們了解了創(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ā)送任何消息。

image

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

image

調(diào)用dispose()之后,和事件序列相關(guān)的資源就會被回收,于是,observer就再也不會訂閱到任何事件了。

接下來,我們就看一個具體的例子。


準備工作

為了演示Observable被回收的效果,我們在Main.storyboard里添加了一個UITextField作為計數(shù)器。App運行后,它將每半秒鐘更新一次。

然后,我們還添加了一個按鈕,我們希望當(dāng)點擊這個按鈕時,回收定時器序列資源,這樣,上面這個計數(shù)器就不會再更新了。

image

為此,我們在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

ViewControllerviewDidLoad方法里,添加下面的代碼:

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
    }

其中subscribeNextsubscribe的作用是類似的,只不過前者只訂閱序列中的.Next事件,并且,可以直接在closure參數(shù)中,使用.Next的associated value。

接下來,按Command + R編譯執(zhí)行,就能看到UITextField中的值不斷變化了。

image

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_tapRxSwiftUIButton的一個擴展,表示按鈕點擊的事件序列(其實這也是一個無限事件序列)。

我們直接訂閱了這個序列的.Next事件(也就是按鈕被點擊的事件)。當(dāng)用戶點擊按鈕時,我們向控制臺打印了一個字符串,并且,調(diào)用了subscriptiondispose方法。

調(diào)用dispose方法可以理解為,再也不需要訂閱了。既然沒人需要訂閱了,interval序列就會被系統(tǒng)回收,再也不會發(fā)送任何事件了。UITextField將也將被固定在一個特定的值上。

image

但是,這種手工調(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里,甚至按鈕點擊的功能也就不好用了,不信你試試?


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容