天天看點

RxSwift-記憶體管理

iOS

中采用的引用計數來管理記憶體,

ARC

中,編譯階段,系統會自動向代碼中插入記憶體管理代碼,無非就是對對象的引用做計數。在

RxSwift

中也仿造了系統引用計數實作了自己的一套引用計數功能。

init() {
#if TRACE_RESOURCES
    _ = Resources.incrementTotal()
#endif
}
    
deinit {
#if TRACE_RESOURCES
    _ = Resources.decrementTotal()
#endif
}
           

需要一張配圖撐撐場面:

RxSwift-記憶體管理

iOS系統已提供了引用計數功能,為何RxSwift還要做這些操作呢?

目的為了快速排出記憶體引用問題是否由

RxSwift

系統産生,在

RxSwift

的使用中,我們建立了很多閉包,儲存閉包等等一系列複雜的操作,避免不了會出現循環引用問題。是以在

RxSwift

中提供了計數總數函數,通過引用總計數可以判斷目前序列是否出現循環應用。

什麼是循環引用?

對象的互相持有,你中有我我中有你,糾纏不清,記憶體會膨脹。即

A -> B -> A

根據引用計數規則,被引用的對象計數會

+1

,在對象的引用指針置

nil

時計數

-1

,作用域結束時

-1

,頁面消失計數

-1

,如局部對象的建立,生命周期結束于局部代碼塊的結束,頁面的生命周期結束于頁面的消失。

如果對象互相引用無法計數清零釋放,會一直占用記憶體,導緻記憶體洩漏。

循環引用在哪些地方容易出現?

  • 頁面跳轉屬性傳值,

    A

    B

    頁面互相持有
  • block用來回調傳值,

    A

    利用

    B

    block

    監聽

    B

    A

    的值,

    block

    内部持有

    A

    來處理資料,出現對象的互相持有
  • RxSwift

    中序列和訂閱者的代碼塊中使用了持有本身的對象

如何解決循環引用問題?

  • [weak self]

    弱引用處理,作用域結束後清除弱引用對象
  • [unowned self]

    無主引用,確定通路時不會被釋放,因為設定後會一直引用對象,即使對象被釋放了也會保持一個無效的引用,如果調用這個引用對象(無效引用)的方法就會崩潰
  • RxSwift

    提供的引用計數幫助我們檢查代碼是否有循環引用,定位問
  • RxSwift.Resources.total

    函數,能夠擷取目前引用計數,通過計數列印觀察RxSwift中是否存在記憶體洩漏問題。操作如下:

1、在工程檔案下的Podfile中添加如下代碼:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    if target.name == 'RxSwift'
      target.build_configurations.each do |config|
        if config.name == 'Debug'
          config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D', 'TRACE_RESOURCES']
        end
      end
    end
  end
end
           

2、執行指令更新

RxSwift

庫:

pod update

3、重新編譯,并插入

RxSwift.Resources.total

代碼,如下:

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidAppear(animated)
    print("RxSwift:\(RxSwift.Resources.total)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("RxSwift:\(RxSwift.Resources.total)")
}
deinit {
    print("目前頁面銷毀:Rx計數:\(RxSwift.Resources.total)")
}
           

通過界面的

push

pop

來觀察計數情況。在

deinit

中因執行順序不同,引用計數和最終結果不一緻,可以通過點選螢幕檢視最終的計數。

非循環引用示例

Observable<Any>.create({ (observer) -> Disposable in
    observer.onNext("1")
    print(self)
    return Disposables.create()
}).subscribe(onNext: { (val) in
    print("\(val)")
}).disposed(by: disposeBag)
           
  • 單向引用,

    AnyObserver->self

    ,結束後

    self

    引用計數減一
  • 列印:頁面正常銷毀

循環引用示例一

Observable<Any>.create({ (observer) -> Disposable in
    observer.onNext("1")
    print(self)
    return Disposables.create()
}).subscribe(onNext: { (val) in
    print("\(val)")
    print(self)
}).disposed(by: disposeBag)
           
  • 列印:頁面沒有銷毀
  • 此處為何有循環引用呢?這裡的循環引用是

    subscribe

    下的閉包引用的

    self

    引起的,

    self

    被訂閱者引用無法釋放,而

    disposeBag

    是在

    self

    釋放後才調用,是以

    sink

    ->

    dispose

    ->

    sink

    循環無法被打破
  • 解決:在

    subscribe

    下的閉包處加入

    [weak self]

    [unowned self]

    即可,注意

    create

    出沒有構成循環引用
Observable<Any>.create({ (observer) -> Disposable in
    observer.onNext("1")
    print(self)
    return Disposables.create()
}).subscribe(onNext: {[unowned self] (val) in
    print("\(val)")
    print(self)
}).disposed(by: disposeBag)
           

循環引用示例二

self.observable = Observable<Any>.create({ (observer) -> Disposable in
    observer.onNext("1")
    print(self)
    return Disposables.create()
})
self.observable!.subscribe(onNext: { (val) in
    print("\(val)")
    print(self)
}).disposed(by: disposeBag)
           
  • 列印:頁面沒有銷毀
  • create

    下的閉包和

    subscribe

    下的閉包中的

    self

    引用都能造成循環引用,與示例一不同在于

    self

    引用了目前序列
  • 解決:在

    create

    subscribe

    下的閉包處加入

    [weak self]

    [unowned self]

    即可
self.observable = Observable<Any>.create({[unowned self] (observer) -> Disposable in
    observer.onNext("1")
    print(self)
    return Disposables.create()
})
self.observable!.subscribe(onNext: {[unowned self] (val) in
     print("\(val)")
    print(self)
}).disposed(by: disposeBag)
           

可以添加

.debug

,檢視

RxSwift

的動作資訊:

self.observable!
    .debug()
    .subscribe(onNext: {[unowned self] (val) in
        print("\(val)")
        print(self)
    }).disposed(by: disposeBag)
           

列印:

2019-08-09 14:03:37.418: FirstController.swift:58 (viewDidLoad()) -> subscribed
2019-08-09 14:03:37.424: FirstController.swift:58 (viewDidLoad()) -> Event next(1)
           

循環引用,要麼

A -> B -> A

簡單的循環引用,要麼是

A -> B -> C -> D -> A

較長的循環鍊的循環引用,順藤摸瓜都能找到問題所在,通過弱引用

[weak self]

或無主引用

[unowned self]

就能解決問題。

繼續閱讀