この記事の内容
- IBOutletで接続しているBarButtonItemがnilになる原因と対策
- navigationItem.rightBarButtonItemやleftBarButtonItemには気をつけよう!
- IBOutletにweakをつけた時とつけなかった時の挙動の違い
この記事のターゲット
- BarButtonItemがいつの間にかnilになるんだけど?!って人
- IBOutletってweakつける意味ある?!って人
- 俺(備忘録)
発生している問題
@IBOutlet** weak **var** barButton: UIBarButtonItem!
のように定義しているオブジェクトがいつの間にかnilになっており、参照しようとするとクラッシュしてしまう。
weak
を外して@IBOutlet var barButton: UIBarButtonItem!
とするとクラッシュしない。
問題の原因
navigationItem.rightBarButtonItem = hogeButton と別のButtonを入れていたから!
詳細
元々storyboard上でnavigationItemのrightBarButtonItemにbarButtonを適用し、NavigationBarの右側にbarButtonが表示されるように設定していた。
しかし仕様上、ある動作をした時に表示されているボタンをbarButtonからhogeButtonへ変更させたいためnavigationItem.rightBarButtonItem = hogeButton
と実装していた。
この時、内部ではrightBarButtonItemからbarButtonが削除される → すなわちNavigationItemからbarButtonに対する強参照が解除されることになり、barButtonがメモリ解放されてしまう。
問題の対策
barButtonの宣言時にデフォルトでついているweakを削除し、barButtonへの参照を強参照とする
また、万が一で循環参照するかもしれないので、barButtonを宣言しているViewControllerのdeinit内でbarButton = nil
を行い参照カウントを減らしておく。
なぜweakを削除するとクラッシュしないの?
先ほどの話では、navigationItem.rightBarButtonItem = hogeButton
としたときに、NavigationItemからbarButtonに対する強参照が解除されるのでbarButtonのメモリ解放が発生すると説明しました。
しかし正確には、NavigationItemからbarButtonに対する強参照が解除されるからbarButtonのメモリ解放が発生するわけではなく、barButtonに対する強参照が解除されたことにより、barButtonの参照カウントが0になるからメモリが解放されます。
そこで、barButtonのweakを外すことで
- NavigationItemからの参照カウント
- UIViewControllerからの参照カウント
の2つが与えられるので、NavigationItemからの参照カウントが無くなってもbarButtonのメモリが解放されることはなくなります。
IBOutletにweakをつけた時とつけなかった時の挙動の違い
おまけですが、IBOutletにweakをつけた時とつけなかった時の違いについて軽く解説します。
weakをつけた時(デフォルト)のメリット
- 循環参照が発生しづらくなる
- weakをつけた対象オブジェクトに対する参照は(一部を除き)すべてが弱参照となるため、循環参照が発生しづらくなる
weakをつけた時(デフォルト)のデメリット
- weakをつけなかった時よりもnilにアクセスする可能性が上がる
- 基本的にはないが、なんらかのきっかけで対象オブジェクトの参照カウントが0になってしまった場合にメモリが解放され、nilにアクセスしてしまう
weakをつけなかった時のメリデメは上記の逆です。
正確に管理しないと循環参照が発生しやすくなる代わりに、nilにアクセする可能性が下がります。