RxSwiftで循環参照してるかもしれない書き方
RxSwiftというよりは、Swiftの話です。
そもそもSwiftの機能として、クロージャの引数とマッチするパラメーターを持つ関数は、 rxSwift.bind(onNext: multiply)
のようにクロージャに引数として渡して使えるようなのですが、「これ、簡潔に書けるけど、循環参照って起こったりしないの?」と思ったので検証しました。
こちら参考にさせていただきました。
RxSwiftでクロージャも無いのにメモリリークさせてた
ソースコード
RxSwiftっぽい感じにして簡単に検証しました。
import Foundation
class ViewController {
var intValue: Int
let rxSwift = RxSwift()
init(initialValue: Int) {
self.intValue = initialValue
print("ViewController init")
}
deinit {
print("ViewController deinit")
}
func multiply(by: Int) {
intValue *= by
print("value is \(intValue)")
}
func multiplyNothing(by: Int) {
// Do nothing
}
func start() {
rxSwift.bind(onNext: multiply)
}
func startNothing() {
rxSwift.bind(onNext: multiplyNothing)
}
func startWeakRef() {
rxSwift.bind(onNext: { [weak self] value in
self?.multiply(by: value)
})
}
func startWeakRefDirect() {
rxSwift.bind(onNext: { [weak self] value in
self?.intValue *= value
print("value is \(self?.intValue)")
})
}
}
class RxSwift {
var _onNext: ((Int) -> Void)?
private var _value: Int = 0
var value: Int {
get {
return _value
}
set (newValue){
_value = newValue
_onNext?(_value)
}
}
init() {
print("RxSwift init")
}
deinit {
print("RxSwift deinit")
}
func bind(onNext: @escaping ((Int) -> Void)) {
_onNext = onNext
}
}
検証1. 「内部でselfを参照する関数をクロージャの引数に渡す」
strong-ref.swift
var vc: ViewController? = ViewController(initialValue: 3)
vc?.start()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
// value is 15
var vc: ViewController? = ViewController(initialValue: 3)
vc?.start()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
// value is 15
循環参照する
検証2. 「selfをキャプチャするクロージャをweak selfで実行する」
weak-ref-direct.swift
var vc: ViewController? = ViewController(initialValue: 3)
vc?.startWeakRefDirect()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
// value is 15
// ViewController deinit
// RxSwift deinit
var vc: ViewController? = ViewController(initialValue: 3)
vc?.startWeakRefDirect()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
// value is 15
// ViewController deinit
// RxSwift deinit
循環参照しない
検証3. 「内部でselfを参照する関数をクロージャの引数に渡す」
weak-ref.swift
var vc: ViewController? = ViewController(initialValue: 3)
vc?.startWeakRef()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
// value is 15
// ViewController deinit
// RxSwift deinit
var vc: ViewController? = ViewController(initialValue: 3)
vc?.startWeakRef()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
// value is 15
// ViewController deinit
// RxSwift deinit
循環参照しない
検証4. 「内部でselfを参照しない関数をクロージャの引数に渡す」
start-nothing.swift
var vc: ViewController? = ViewController(initialValue: 3)
vc?.startNothing()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
var vc: ViewController? = ViewController(initialValue: 3)
vc?.startNothing()
vc?.rxSwift.value = 5
vc = nil
// RxSwift init
// ViewController init
循環参照する
なぜか
selfがキャプチャされるから。(当たり前)
普段コンパイラに頼りきっていて、「[weak self]をつけろって言われない書き方出来てるし、これで大丈夫やろ!」と思ってしまいそうになりますが、@escaping
かどうかはちゃんと見るようにしましょう(自戒
本家のbind(onNext:)
ちゃんと@escaping
書かれてて強参照が明示的になってますね。
/**
Subscribes an element handler to an observable sequence.
In case error occurs in debug mode, `fatalError` will be raised.
In case error occurs in release mode, `error` will be logged.
- parameter onNext: Action to invoke for each element in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
public func bind(onNext: @escaping (Self.E) -> Swift.Void) -> Disposable
どうすれば良いのか
関数に処理を切り出すにしても、selfを参照するコードは全て[weak self]にしておくと間違い無いです。
([unowned self]でもいいという記事も見かけたので、絶対そうとは言い切りません)
Author And Source
この問題について(RxSwiftで循環参照してるかもしれない書き方), 我々は、より多くの情報をここで見つけました https://qiita.com/yosshi4486/items/a7760554b256787e0f53著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .