🪬

SwiftのARCとメモリ管理 循環参照を避ける方法

に公開

この記事でわかること

循環参照がなぜ起こるか
weakをつける理由

メモリ管理

プログラムはずっとメモリ上にいると新しいインスタンスがメモリに入れなくなるという物理的制約がある。
iOSアプリ開発においても同様で適切に管理されていないメモリは、アプリのパフォーマンスを低下させ、クラッシュの原因となります。

SwiftではARC(Automatic Reference Counting)というシステムでメモリ管理を行なっています。

ARCの基本

ARCは、オブジェクトが使用されている間はメモリ上に保持し、不要になった時点で自動的に解放する仕組みです。これにより、開発者は手動でメモリ管理を行う必要がなくなり、より安全なコードを書くことができます。

ARCの動作原理:

  1. クラスのインスタンスが生成されると、メモリが確保されます。
  2. そのインスタンスが参照されるたびに、参照カウントが1増加します。
  3. 参照がなくなると(つまり参照カウントが0になると)、メモリが解放されます。

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/

循環参照の問題

しかし、ARCには落とし穴があります。それが「循環参照」です。

class Hoge {
    var fuga: Fuga?
}

class Fuga {
    var hoge: Hoge?
}

class App {
    func application() {
        let hoge = Hoge()
        let fuga = Fuga()
        
        hoge.fuga = fuga  // hogeがfugaを強参照
        fuga.hoge = hoge  // fugaがhogeを強参照
    }
}

この例では、HogeFugaが互いに強い参照を持っています。
クラスなどのスコープ内でお互いにインスタンスを参照しあってると参照カウンタが0にならず、クラスもインスタンスもメモリから削除できない状態です。

循環参照の解決策:weak参照

循環参照を解決するには、weakキーワードを使用します。

class Hoge {
    weak var fuga: Fuga?
}

class Fuga {
    var hoge: Hoge?
}

class App {
    func applecation() {
        let hoge = Hoge()
        let fuga = Fuga()
        
        hoge.fuga = fuga
        fuga.hoge = hoge
    }
}

weakを使用すると、そのプロパティは参照カウントを増加させません。これにより、循環参照が解消され、適切にメモリが解放されます。

クロージャでの注意点

クロージャ内でselfを使用する際も、循環参照に注意が必要です。特にUIKitを使用する際のアラート表示などでよく見ます。

class ViewController: UIViewController {
    func showAlert() {
        let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in
            self?.dismiss(animated: true, completion: nil)
        })
        present(alert, animated: true, completion: nil)
    }
}

クロージャの{}は別世界、参照カウンタを増やしている。
ViewControllerクラスを終了させるときにクロージャーで自分自身を参照していまっているから参照カウンタを0に出来ずメモリから消せずに、メモリリークを起こすのでweakをつける。

おわりに

deinitメソッドを活用して、オブジェクトが適切に解放されているか確認できます。

class Hoge {
    weak var fuga: Fuga?
    deinit {
        print("解放!!")
    }
}

class Fuga {
    var hoge: Hoge?
}

class App {
    func applecation() {
        let hoge = Hoge()
        let fuga = Fuga()
        

        hoge.fuga = fuga
        fuga.hoge = hoge
    }
}
var app = App()
app.applecation()

参考

https://speakerdeck.com/oyuk/swiftdenande-weak-self-surufalseka?slide=10

https://qiita.com/rockname/items/b00d52c9bc49603f99a5

Discussion