Swift weakプロパティを持つUIViewControllerを再利用する時は注意しよう

個人開発したアプリの宣伝
目的地が設定できる手帳のような使い心地のTODOアプリを公開しています。
Todo with Location

Todo with Location

  • Yoshiko Ichikawa
  • Productivity
  • Free

Swift weakプロパティを持つUIViewControllerを再利用する時は注意しよう。という話し。

概要

Storyboardからoutlet接続されたViewを持つUIViewControllerを再利用しようとした時、outlet接続されたプロパティがnilになる時があります。


再利用されるViewController

class ReuseViewController: UIViewController{
    @IBOutlet weak var myView: UIView!
    ....
}

再利用を行うViewController

class ViewController: UIViewController{
    var cacheViewController: ReuseViewController?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.cacheViewController = storyboard?.instantiateViewController(withIdentifier: "ReuseViewController") as! ReuseViewController
    }

    func presentCacheViewController(){
        if let vc = self.cacheViewController {
            present(self.cacheViewController, animated: true, completion: nil)
        }
    }
}

上記のように一度生成したViewControllerを保管しておき、再度利用するようなケース、例えばcontainerViewなんかでは使うケースがあるんじゃないかな。

この時、weak自身には参照はカウントを持たないのでARCで破棄されているケースがある。当然、中の処理で参照していたりするとクラッシュしたりViewが表示されないといったことになるので注意。

対応策
  • weakをなくし強参照とする。この場合、循環参照に注意。
  • または、weak制約のまま参照を持つようにする
class ViewController: UIViewController{
    var cacheViewController: ReuseViewController?
    var cacheView:UIView?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.cacheViewController = storyboard?.instantiateViewController(withIdentifier: "ReuseViewController") as! ReuseViewController
        self.cacheView = self.cacheViewController.myView
    }
    ....
}

これで理屈としては消えないはず。

また、以下のようなことをしても

if myView == nil {
    myView = UIView()
}

weak参照の場合、参照を持たないインスタンスを代入しても、代入時点でインスタンスが破棄されるので解決策とはならない。

普段、脳死でweakをつけるので変わったことをするとハマった話し。