はじめに
以下のコードを見たときに違和感を覚えたので、検証した結果を書きます。
違和感を覚えたのは以下のコードです。
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などが呼ばれていると思うのでその時点で解放されるかと思います。