Swiftでクロージャが入れ子の場合のキャプチャリスト

はじめに

Swiftのクロージャには循環参照を回避するための仕組みとしてキャプチャリストがあります。

最近、キャプチャリストがあるのにメモリリークするというバグに遭遇して、自分の認識が間違っていたことが分かったのでメモしておきます。

結論としては、クロージャ入れ子の場合には、直接使っていなくても外側にキャプチャリストを書いておかないと強参照になります。

バージョン

Swift 4.0

サンプルコード

class Hoge {
    deinit {
        print("deinit")
    }
    let n: Int
    
    init(n: Int) {
       self.n = n
    }
    // 内側だけweak
    lazy var closure1: () -> Int = {
        return {[weak self] in return (self?.n)! * 2}()
    }
    // 外側だけweak
    lazy var closure2: () -> Int = { [weak self] in
        return {return (self?.n)! * 2}()
    }
    // 両方weak
    lazy var closure3: () -> Int = { [weak self] in
        return {[weak self] in return (self?.n)! * 2}()
    }
    // 内側だけunowned
    lazy var closure4: () -> Int = {
        return {[unowned self] in return self.n * 2}()
    }
    // 外側だけunowned
    lazy var closure5: () -> Int = { [unowned self] in
        return {return self.n * 2}()
    }
    // 両方unowned
    lazy var closure6: () -> Int = { [unowned self] in
        return {[unowned self] in return self.n * 2}()
    }
}


do {
    // deinit呼ばれない
    print("closure1")
    let hoge = Hoge(n: 100)
    print(hoge.closure1())
}

do {
    print("closure2")
    let hoge = Hoge(n: 100)
    print(hoge.closure2())
}

do {
    print("closure3")
    let hoge = Hoge(n: 100)
    print(hoge.closure3())
}

do {
    // deinit呼ばれない
    print("closure4")
    let hoge = Hoge(n: 100)
    print(hoge.closure4())
}

do {
    print("closure5")
    let hoge = Hoge(n: 100)
    print(hoge.closure5())
}

do {
    print("closure6")
    let hoge = Hoge(n: 100)
    print(hoge.closure6())
}