内存管理--循环引用
2016-06-09 14:00
246 查看
内存管理可以追溯到手动内存管理(Manual Retain Release,简称 MRR)。在 MRR,开发者创建的每一个对象,需要声明其拥有权,从而保持对象存在于内存中,当对象不再需要的时候撤销拥有权释放它。MRR 通过引用计数系统实现这套拥有权体系,也就是说每个对象有个计数器,通过计数加1表明被一个对象拥有,减1表明不再持有。当计数为零,对象将被释放。由于手动管理内存实在太烦人,因此苹果推出了自动引用计数(ARC)来解放开发者,不再需要开发者手动添加 retain 和 release 操作,从而可以专注于 App 开发。在 ARC,开发者将会定义一个变量为“strong”或“weak”。一个 weak 弱引用无法 retain 对象,而 strong 引用会 retain 这个对象,并将其引用计数加一。

我为什么要关心这些?
ARC 的问题是循环引用很容易发生。当两个不同的对象各有一个强引用指向对方,那么循环引用便产生了。试想下,一个 book 对象持有多个 page 对象,每个 page 对象又有个属性指向它所属的 book 对象。当你释放了持有 book 和 page 对象的变量时,他们仍然还有强引用指向各自,因此你无法释放他们的内存,即使已经没有变量持有他们。
不幸的是,循环引用在实际中并没有那么容易被发现。多个对象之间(A 持有 B,B 持有 C,C 也恰好持有 A)也可以产生循环引用。更糟的是,Objective-C block 和 Swift 闭包都是独立内存对象,它们会持有其所引用的对象,于是就引发了潜在的循环引用问题。
循环引用对 app 有潜在的危害,会使内存消耗过高,性能变差和 app 闪退等。然而,苹果文档对于可能发生循环引用的场景以及如何避免并没有详细描述,这就容易导致一些误解和不良的编程习惯。
父子对象关系
父子对象关系是一个循环引用的典型案例,不幸的是,它也是唯一一个存在于苹果文档中的案例。其实就是前文描述的 Book 与 Page 案例。典型的解决方法就是,在子类定义一个指向父类的变量,声明为 weak 弱引用,从而避免循环引用。
class Parent {
var name: String
var child: Child?
init(name: String) {
self.name = name
}
}
class Child {
var name: String
weak var parent: Parent!
init(name: String, parent: Parent) {
self.name = name
self.parent = parent
}
}
当 Block 和闭包包含在类的成员变量中
另外一个典型的例子,可能不是那么直观。如我们前面解释的,闭包和 block 都是独立的内存对象,会 retain 它们所引用的对象,因此如果我们有个类,里面有个闭包变量,并且这个闭包恰好引用了自身所属对象的一个属性或方法,那么就可能产生循环引用,因为闭包会创建强引用捕获“self”。
class MyClass {
lazy var myClosureVar = {
self.doSomething()
}
这个案例的解决方法是定义一个弱版本的 self,然后在闭包或 block 中使用。在 objective-C,我们会定义一个新的变量:
(id) init() {
__weak MyClass * weakSelf = self;
self.myClosureVar = ^{
[weakSelf doSomething];
}
}

我为什么要关心这些?
ARC 的问题是循环引用很容易发生。当两个不同的对象各有一个强引用指向对方,那么循环引用便产生了。试想下,一个 book 对象持有多个 page 对象,每个 page 对象又有个属性指向它所属的 book 对象。当你释放了持有 book 和 page 对象的变量时,他们仍然还有强引用指向各自,因此你无法释放他们的内存,即使已经没有变量持有他们。
不幸的是,循环引用在实际中并没有那么容易被发现。多个对象之间(A 持有 B,B 持有 C,C 也恰好持有 A)也可以产生循环引用。更糟的是,Objective-C block 和 Swift 闭包都是独立内存对象,它们会持有其所引用的对象,于是就引发了潜在的循环引用问题。
循环引用对 app 有潜在的危害,会使内存消耗过高,性能变差和 app 闪退等。然而,苹果文档对于可能发生循环引用的场景以及如何避免并没有详细描述,这就容易导致一些误解和不良的编程习惯。
父子对象关系
父子对象关系是一个循环引用的典型案例,不幸的是,它也是唯一一个存在于苹果文档中的案例。其实就是前文描述的 Book 与 Page 案例。典型的解决方法就是,在子类定义一个指向父类的变量,声明为 weak 弱引用,从而避免循环引用。
class Parent {
var name: String
var child: Child?
init(name: String) {
self.name = name
}
}
class Child {
var name: String
weak var parent: Parent!
init(name: String, parent: Parent) {
self.name = name
self.parent = parent
}
}
当 Block 和闭包包含在类的成员变量中
另外一个典型的例子,可能不是那么直观。如我们前面解释的,闭包和 block 都是独立的内存对象,会 retain 它们所引用的对象,因此如果我们有个类,里面有个闭包变量,并且这个闭包恰好引用了自身所属对象的一个属性或方法,那么就可能产生循环引用,因为闭包会创建强引用捕获“self”。
class MyClass {
lazy var myClosureVar = {
self.doSomething()
}
这个案例的解决方法是定义一个弱版本的 self,然后在闭包或 block 中使用。在 objective-C,我们会定义一个新的变量:
(id) init() {
__weak MyClass * weakSelf = self;
self.myClosureVar = ^{
[weakSelf doSomething];
}
}
相关文章推荐
- 页面缓存:内存和文件之间的那些事
- IE7降低内存和降低CPU的几个技巧
- 如何高效的使用内存
- DOS下内存的配置
- Lua的内存管理浅析
- XP/win2003下发现1G的内存比512M还慢的解决方法
- PowerShell实现动态获取当前脚本运行时消耗的内存
- C#实现把dgv里的数据完整的复制到一张内存表的方法
- SQL语句实现查询SQL Server内存使用状况
- C语言内存对齐实例详解
- 深入学习C语言中memset()函数的用法
- 全局变量与局部变量在内存中的区别详细解析
- VB读取线程、句柄及写入内存的API代码实例
- php运行提示:Fatal error Allowed memory size内存不足的解决方法
- 跟我学习JScript的Bug与内存管理
- IE浏览器IFrame对象内存不释放问题解决方法
- C#之CLR内存深入分析
- JavaScript 变量、作用域及内存
- 跟我学习javascript的垃圾回收机制与内存管理
- JavaScript避免内存泄露及内存管理技巧