在
iOS
中采用的引用計數來管理記憶體,
ARC
中,編譯階段,系統會自動向代碼中插入記憶體管理代碼,無非就是對對象的引用做計數。在
RxSwift
中也仿造了系統引用計數實作了自己的一套引用計數功能。
init() {
#if TRACE_RESOURCES
_ = Resources.incrementTotal()
#endif
}
deinit {
#if TRACE_RESOURCES
_ = Resources.decrementTotal()
#endif
}
需要一張配圖撐撐場面:
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中是否存在記憶體洩漏問題。操作如下:RxSwift.Resources.total
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]
就能解決問題。