はじめに
以下のコードを見たときに違和感を覚えたので、検証した結果を書きます。
違和感を覚えたのは以下のコードです。
class UserViewController: UIViewController {
let disposeBag = DisposeBag()
private func loadUser(user:User){
API.User.Get(user.id).subscribeNext { user in
self.userNameLabel.text = user.name
}.addDisposableTo(disposeBag)
}
}
何が起こるのか?
DisposeBag
はObservable
を自動的に解放するための便利な機能です。
はじめてのRxSwiftのメモに書いてますので、見てください。
「このDisposeBag
によってself
が解放されるはず。」
という意図で書かれたコードだと思いますが、実際は意図とは違う潜在バグを引き起こすコードでした。
このコードは正常に動きますが、deinit
が呼ばれません。
すなわち、UserViewController
が解放されません。
そのうちメモリリークが発生しますが、開発環境では気づきにくいでしょう。
何が起こっているのか?
問題は以下のコードにあります。
API.User.Get(user.id).subscribeNext { user in
self.userNameLabel.text = user.name
}.addDisposableTo(disposeBag)
このコードの中でself
を参照しているため、self
が解放されません。
self
が解放されないとdisposeBag
も解放されないため、結果的に相互参照が発生してしまいます。
解決策
解決策は簡単で以下のようにself
を弱参照すれば良いだけです。
class UserViewController: UIViewController {
let disposeBag = DisposeBag()
private func loadUser(user:User){
API.User.Get(user.id).subscribeNext { [weak self] user in
self?.userNameLabel.text = user.name
}.addDisposableTo(disposeBag)
}
}
このようにする事でself
が解放されるためdisposeBag
も同時に解放されObservable
も解放されます。
(API.User.Get
の中でonCompleted
などが呼び出されていれば、addDisposableTo(disposeBag)
は不要です。)
最後に
基本的な事ですが、新しい概念(ここで言うとDisposeBag
)が入ると混乱して基本的な事が抜けてしまう事があります。
僕もコードを見たときに感じたのは違和感だけで、確証は得られなかったので検証は大切だと思いました。
未検証のもの
上記のコードは以下のように書いても解放されると思います。
class UserViewController: UIViewController {
let disposeBag = DisposeBag()
private func loadUser(user:User){
_ = API.User.Get(user.id).subscribeNext { [weak self] user in
self?.userNameLabel.text = user.name
}
}
}
上記のコードはAlamofire
をRxSwift
を使用しています。
Manager.sharedInstance#rx_request
を使ってます。
多分Manager.sharedInstance
がObservable
を保持して、リクエストが終わった時点でonCompleted
などが呼ばれていると思うのでその時点で解放されるかと思います。