Posted at

unownedの使いどころ

More than 1 year has passed since last update.

Swiftでクロージャーを利用する場合、循環参照をしないようにself(場合によってはself以外の変数も)を弱参照にしますが、その時weak or unownedどちらにするべきか迷うことがあると思います。

基本的には困ったらweakにして置くのがベターですが、unownedを使えれば記述も少し短くなるのでその例を紹介します。


unownedを使える条件

循環参照するかつクロージャーが呼ばれる時にその対象が解放されていない場合です。

解放されている状態で万が一その対象にアクセスするとクラッシュします。

クラッシュする例

class SomeVC: UIViewController {

func apiRequest() {
Session().send() {[unowned self] in
// ネットワークの状況によっては数秒後にクロージャーがコールされる
// Backボタン等でこのVCが解放されていたらselfにアクセスした時点でクラッシュする
self.handleAPIResponse()
}
}
}

クロージャーの呼び出される期間がオブジェクトの生存期間と同じであればこのようなことは起きませんが、ネットワークアクセスやアプリで使い回すシングルトンのモジュールなどはどうしても生存期間が違ってきます。


unownedを使えるパターン


パターン1:オブジェクトの解放されるタイミングで明示的にクロージャーの呼び出しを無効化する場合

class SomeVC: UIViewController {

var task: SessionTask?
var object = Object()
deinit {
task?.cancel()
object.completion = nil
}
func apiRequest() {
task = Session().send() {[unowned self] in
// ネットワークの状況によっては数秒後にクロージャーがコールされる
// deinitでcancelされてるのでselfが解放されるとここは呼ばれないのでクラッシュしない
self.handleAPIResponse()
}

object.completion = {[unowned self] in
// deinitでobject.completionにnilを代入しているので解放後ここは呼ばれずクラッシュしない
self.someAction()
}
object.longTimeMethod()
}
}


パターン2:オブジェクトの生存期間とクロージャーの呼び出される期間が同じ場合

通常のSwiftではあまりないですが、RxSwiftを使う時に使えます。

import RxSwift

class SomeVC: UIViewController {
let vm = SomeViewModel()
let disposeBag = DisposeBag()
func viewDidLoad() {
vm.observableObj.subscribe(onNext: { [unowned self] (_) in
// disposeBagが解放されると、このクロージャーも無効化されるので解放後ここは呼ばれずクラッシュしない
self.someMethod()
}).addDisposableTo(disposeBag)
}
}

パターン1はdeinitに毎回書くのがめんどいのであまりやりませんが、unownedを使うとselfに直接アクセスでき、ちょっとだけ記述が楽になるので問題がない時は積極的に使ってみるのもいいと思います。