Swift - 在闭包中强引用self后预计会有内存泄漏

7

请问为什么这个代码不会泄漏?

我在闭包中捕获了self,这样就有两个指向彼此的强引用,因此应该永远不会调用Person对象的deinit方法。

首先,这是我的Person类:

class Person {
    var name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deinitialized") }
}

这是我的视图控制器实现

class ViewController: UIViewController {

    var john:Person?

    func callClosureFunction( closure:(name:Bool) -> () ) {
        closure(name: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        john = Person(name:"John")

        self.callClosureFunction { (name) in

            self.john?.name = "John Appleseed"
            self.john = nil

            // xcode prints - John Appleseed is being deinitialized
        }

    }

}

我原本希望通过以下方法解决问题:

self.callClosureFunction { [weak self] (name) in ...

但其实这并不必要。为什么呢?
3个回答

6

因为您的视图控制器未对闭包进行保留,所以不存在循环引用问题。如果您这样编写:

class ViewController: UIViewController {

    var john:Person?
    var closure:(Bool)->()? 

    func callClosureFunction( closure:((name:Bool) -> ())? ) {
        closure?(name: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        john = Person(name:"John")
        closure = { (name) in

            self.john?.name = "John Appleseed"    

            // Because this closure will never be released, the instance of Person will never deinit either
        }
        self.callClosureFunction(closure) 
    }  
}

如果视图控制器保留闭包,而闭包通过其对self的引用保留视图控制器,则两者都不会被释放。因此,如果您没有明确设置self.john = nil(这在原始示例中已经完成),则Person实例将永远不会调用deninit
在不必要时不适当地在闭包中使用弱self是非常常见的(这实际上可能导致一些难以理解的错误)。要记住的关键规则是,在ARC下,弱引用通常不是默认值。通常应该使用强引用,除非它会导致循环引用,此时才应该使用弱引用来打破循环引用。闭包也是如此:强self应该是默认值,除非在这种情况下self本身还对闭包有强引用。

1
John AppleseedPerson对象仍将被释放。请查看我的答案。 - Alexander
如果闭包保留了VC (self),而VC又像我例子中一样保留了闭包,那么VC就永远不会被释放(闭包也不会)。如果VC从未去初始化,那么它也不会释放对Person对象的引用,这意味着Person对象也永远不会去初始化。 - Daniel Hall
1
不需要担心循环引用。john 被明确设置为 nil,移除了最后一个引用,因此导致 ARC 将其释放。 - Alexander
1
@AlexanderMomchliov 哦,没错 - 我漏掉了这个好点子。我更新了我的答案以反映这一点。谢谢! - Daniel Hall

0

你正在捕获指向ViewControllerself,但你想知道关于Person实例的情况。

Person实际上没有循环引用,因此当你在闭包末尾将其设置为nil时,它会被正确地解除初始化和释放。

实现ViewControllerdeinit并查看其效果。


0
我在闭包中捕获了self,这样我就有两个强指针相互指向,因此,Person对象的deinit消息不应该被调用。
不,你只有一个强指针,从闭包到self。没有从闭包返回self的循环引用。因此,你有一个有向无环图,对ARC来说没有问题。
然而,你的实验从一开始就是有缺陷的。即使闭包被捕获,John Appleseed Person对象仍将被释放。这个对象的生命周期完全依赖于ViewController中的john引用。当你将该引用设置为nil时,你删除了对John Appleseed对象的最后一个引用,因此它被释放。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接