不管什么语言,内存管理始终是重中之重,所以Swift
也不例外
Swift
采用了和Objective - C
相同的ARC
机制来管理内存,大部分时候内存问题不需要我们关心,基本上做到内存自动管理。基本规则是这样的:当一个对象的引用计数为0的时候,这个对象就会被释放/自动回收。所以在我们编码的过程中如果需要手动释放一个对象,只需要将对象置为nil即可。但是Swift
和Objective - C
一样,会遇到循环引用(retaincycle)
和block
导致的内存问题。
关于Objective - C
的内存管理,你可以出门左转,看我以前的一篇文章,详细的讲解了OC的内存管理.
什么是循环引用?
先看一段代码:
import UIKit
class A {
let b: B
init() {
b = B()
b.a = self
}
deinit {
print("A is deinited")
}
}
class B {
var a: A? = nil
deinit {
print("B is deinited")
}
}
var test: A? = A()
test = nil
我们把test
置为nil
以后,上面的两句print
应该会被执,然而上面的代码并没有执行。也就是说,test
并没有被释放。因为在即使你把test置为nil,而A里面的b还继续持有A,导致无法被释放,造成内存泄露...
那么我们来仔细的分析以下上面的循环引用的关系,在test
对象初始化的时候,生成了一个B实例,并存储在b中。然后我们又将A赋值给了b.a
。这样a.b
和b.a
形成了一个循环引用。所以就算你将test
置为nil
,b仍然持有A,导致无法被释放,并且以后再不会被访问到,除非你杀掉进程,那么如何解决呢 ?
防止循环引用
在Objective - C
中使用weak
关键修饰可能会引起循环引用的属性,就能打破循环引用。在Swift
中同样也有一个weak
,也能打破循环引用,所以我们把上面的代码修改成如下代码就能打破循环引用:
class B {
weak var a: A? = nil
deinit {
print("B is deinited")
}
}
这样一来,当test
被置为nil
的时候,A实例就不再被持有了,那么该实例被释放,紧接着a.b
被释放,那么b.a
也同样得到释放,所以就可以输出:
A is deinited
B is deinited
在Swift
中还有一个unowned
, unowned
和weak
是有区别的,看看官方的解释:
“If the captured reference will never become nil, it should always be captured as an unowned reference, rather than a weak reference.”
很简单的解释:如果该引用不会变成nil
,那么你应该使用unowned
引用,而不是weak
。所以unowned
其实有点儿像Objective - C
里面的unsafe_unretained
。而且这里有一点需要注意:不要使用weak
去修饰一个let
,因为该引用之后会变,所以只能是var
,这一点在官方文档里面也有提示:
“Weak references must be declared as variables, to indicate that their value can change at runtime. A weak reference cannot be declared as a constant.”
上述情况最常见的出现场景就是使用delegate
的时候,我们通常会把delegate
属性用weak
修饰
weak var delegate: TestDelegate
闭包引起的循环引用
在Swift
中的闭包(closure)
和Objective - C
中的block
一样,会引起循环引用,从而导致内存泄露。
原因是凡是被闭包或者block
所引用,那么闭包和block
会自动持有它。这是一个比较隐蔽的陷阱,很多新手会犯这样的错误而不自知。看一段官方的示例代码:
“class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}”
上面代码的持有关系很明显,HTMLElement
持有asHTML
属性,而在后面的闭包中有使用了self
关键字,那么asHTML
又反向持有HTMLElement
,这样就形成了循环引用,同样的可以使用weak
来打破这个循环引用:
lazy var asHTML: () -> String = {
[weak self] in
if let strongSelf = self { // 这个一定要加
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
}
这里可能很多人会有疑问,可不可以用unowned
?为何还要加一个strongSelf
判断?我在上一篇的OC内存管理面已经讲过为何要加strongSelf
的判断,这里也是差不多一个意思,为了防止提前释放。
如果我们可以确保整个初始化的过程中self
,不会被释放掉,那么我们可以放心的时候用unowned
,同时也可以去掉strongSelf
的判断。如果不能保保证的话,还是老实的使用weak
吧,不然会崩的让你各种疼...
到这里就会引出另一个很有意思的问题,那就是是不是所有的闭包都得加上[weak self]
?我曾维护过一份蛋疼的代码,那个家伙在所有的block
外面都加上了弱引用,也就是但凡遇到block
你都能看到这句话,包括系统的各种block
:
__weak typdef(self) weakSelf = self;
好吧,你赢了。真的不累么?
其实,如果你能搞清楚,对象间的持有关系,就不会这么干了。有很多block/closure
里面不需要加[weak self]
,所以不要干这么低级的事情
生命不息,折腾不止...
I'm not a real coder,but i love it so much!