swift 闭包引用循环中的迷魂阵

         相信大家都知道,闭包使用不当,会造成引用循环,从而造成App的内存泄露。但是有时候,看起来会造成引用循环的代码实际上并没有造成引用循环。这个需要我们正确的区分。

【栗子1】请大家看看下面的代码,分析请看注释:

class ClassA {
    let power =2
    var transform: (Int ->Int)?
    var dataArr = [1,2,3,4]
    deinit {
    print("ClassA实例被销毁")
    }
}


var a: ClassA =ClassA()   // ClassA实例的引用计数为1
a.transform = {  numin
    returna.power * num   //闭包捕获a, 则ClassA实例的引用计数 增加1,现在为2 ;同时,a.transform指向该闭包,闭包的引用计数为1
}
a.transform!(5)
a = ClassA()<span style="font-family: Arial, Helvetica, sans-serif;"> //将a重新赋值。原来的ClassA实例的引用计数减一。原来的ClassA实例的引用计数为1。</span>

       由此得出结论:运行的最后,原来的ClassA实例持有一个指向闭包的引用;闭包也持有一个指向原来ClassA实例的引用。从而造成引用循环。

       似乎如此。如图:


                                                       图 1-1

       但是,当我们把这段代码拿到playground上运行,结果却发现我们的判断是错误的——最先创建的那个ClassA实例被正常销毁了。为什么会这样呢?

       原因就是,闭包捕获的是闭包外部变量的引用,是引用,是引用,重要的事情说3遍!如果那个变量本身是引用,那么,闭包捕获的就是那个引用的引用了。所以在这里,闭包捕获的是变量a的引用,并不是ClassA实例的引用。也就没有造成引用循环。如图


                                                       图 1-2


         相信大家现在明白了吧。

【栗子2】接下来,大家看看这个代码:

class ClassA {
    let power =2
    lazyvar transform: (Int ->Int) = {num in num *self.power}
    var dataArr = [1,2,3,4]
    deinit {
    print("ClassA实例被销毁")
    }
}

var a =ClassA()
a.transform(8)
a =ClassA()

       这段代码跟例子1的代码很相似。但是在闭包里用的是self。在playground上运行,发现最开始的ClassA实例没有被正常销毁,这是由于其真正造成了内存泄露。为什么呢?

       self,代表当前的实例。所以,在闭包中使用了self,使得闭包捕获了当前实例的引用,从而持有了指向当前实例的引用。从而造成引用循环。如图


                                                       图 2-1

【栗子3】可能有人打印过闭包内外变量的地址,发现它们的地址是一样的。如图



                                                                                        图 3-1

       当我们分别在闭包外和闭包内打印变量a的地址的时候,发现这两个的地址一样的。这个可以在inout关键字中一样。如图


                                                                                        图 3-2


       swift 中,inout是将值传递给函数,在函数执行完后,将这个值替换掉原来的那个值。从而达到使用inout能起到引用传递修改值的效果。我们在函数内外都对实参的地址进行了打印,结果两者是一摸一样的。图3-1中,闭包捕获的参数跟外部的地址一样,估计是苹果做了些处理。

备注:本文下的Xcode的版本为7.3.1。






已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页