Fork me on GitHub

如何打破Swift中的循环引用

Swift的内存管理也是使用的ARC(自动引用计数):当我们初始化创建一个对象实例的时候,swift就会替我们管理和分配内存,此时的引用计数为1,当对其进行init(copy/mutableCopy)时,引用计数会+1,而当实例被销毁时,引用计数就会-1。当系统检测到引用计数为0的时候,就会释放掉这个内存。

但是,这种引用计数会产生一个问题就是循环引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {
var b: B?

init() {
print("A初始化")
}
deinit {
print("A析构掉")
}
}

class B {
var a: A?

init() {
print("B初始化")
}
deinit {
print("B析构掉")
}
}

var a: A?; a = A()
var b: B?; b = B()

a!.b = b; b!.a = a
a = nil; b = nil

你会发现,A和B的析构函数deinit都没有调用,因为当a执行析构的时候,b.a还在对其进行引用,当b析构的时候,a.b也在对b进行引用。这时候解决的方法就是对其中的某一个声明进行若引用,即加上weak:

1
weak var b: B?

另外一种造成循环引用的问题就是闭包:闭包中对任何元素的引用都会被闭包自动持有,如果我们在闭包中需要使用self的话,那就相当于闭包对self持有,而block又是被self直接或间接持有,这样就造成了循环引用。例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class C {
var name: String

lazy var block: ()->() = {
print(self.name )
}
init(name: String) {
self.name = name
print("C初始化")
}
deinit {
print("C析构")
}
}

var c: C? = C(name: "c")
c?.block()
c = nil

这里C的析构函数也是没有执行的。block是self的属性,block里面又对self持有,这就形成了循环引用。所以这里我们可以使用unowned,也可以使用weak:

1
2
3
4
5
6
7
8
9
10
11
// unowned
lazy var block: ()->() = { [unowned self] in
print(self.name)
}

// weak
lazy var block: ()->() = { [weak self] in
if let strongSelf = self {
print(strongSelf.name)
}
}

那么这两个使用有什么区别呢?接下来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class C {
var name: String

lazy var block: ()->() = { [unowned self] in
print(self.name)
}
init(name: String) {
self.name = name
print("C初始化")
}
deinit {
print("C析构")
}
}

class D {
var block: (()->())!

init(callBack: (()->())?) {
self.block = callBack!
print("D构造")
}
deinit {
print("D析构")
}
}

var c: C? = C(name: "c")
var d = D.init(callBack: c?.block)
c!.block()
c = nil
d.block()

这里当你运行到 d.block()的时候,是会有一个error。

因为当d.block()执行的时候,c已经被析构掉了,而闭包里的self肯定也是不存在的,是一个nil,这个时候执行的话self.name就会报错。所以在我们不确定是否有外部变量在持有这个block的时候,我们就应该使用weak更为安全,因为使用weak的话self.name需要改成可选性的self?.name,这个时候self?.name肯定就为nil了。所以换成weak之后,在playground里的d.block()就不会有错误了,而且block也是会正常执行的,只不过print(self?.name)打印出来为nil。

最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class C {
var name: String

lazy var block: ()->() = { [weak self] in
print(self?.name ?? "nothing")
}
init(name: String) {
self.name = name
print("C初始化")
}
deinit {
print("C析构")
}
}

class D {
var block: (()->())!

init(callBack: (()->())?) {
self.block = callBack!
print("D构造")
}
deinit {
print("D析构")
}
}

var c: C? = C(name: "c")
var d = D.init(callBack: c?.block)
c!.block()
c = nil
d.block()
------------- 本文结束感谢您的阅读 -------------