読者です 読者をやめる 読者になる 読者になる

Pebble's Diary

プログラマーの作業メモ

swift 循環参照

swift 循環参照の一例を挙げます。

iOSのソースを例にとります。

ViewController.swift

import UIKit
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let model = Model()
        model.action()
    }
}

Model.swift

import Foundation

class Model {
    private let message:String = "hello"
    private var myclosure:((Void)->Void)?
    
    init()
    {
        print("Model init")
        
        myclosure = {
            print("\(self.message)")
        }
    }
    
    deinit
    {
        print("Model deinit")
    }
    
    func action(){
        myclosure?()
    }
}

ここでmodelはローカル変数ですから、viewDidLoadを抜けた時は破棄されるはずです。 しかし結果は以下のようになり、"Model deinit"が表示されませんからdeinitが呼ばれず、破棄されていないことが分かります。

Model init
hello

原因は、myclosureがselfを強参照した結果、modelがmyclosureを、myclosureがmodelを互いに参照してるためです。 ガベージコレクション方式でない、リファレンスカウンタ方式のメモリ管理方式を採用している言語ではこのようなことが起こります。 Modelのinit()を以下のように修正すると、破棄されるようになります。

init()
{
    print("Model init")
       
    myclosure = { [unowned self] in // <- 修正
        print("\(self.message)")
    }
}

結果

Model init
hello
Model deinit

このケースでは、actionメソッドを実行中にmodelのインスタンスがなくなってしまうことはありませんので、 [unowned self]を使えばよいです。
ただし、actionメソッドを実行中にmodelのインスタンスnilになってしまうことがあるような複雑なケースの場合は、[weak self]を使います。この場合、少しタイピング量が増えて、以下のような書き方になります。

init()
{
    print("Model init")
        
    myclosure = { [weak self] in 
        if let weakself = self { 
            print("\(weakself.message)")
        }
    }
}

selfがnilになってしまっても、オプショナルで判定して処理をスキップすることができます。