五、内存管理

2018-11-07  本文已影响4人  那样风采

引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。不管是Objective-C语言还是Swift语言,其内存管理方式都是基于引用计数的。

1、引用计数

1.1 介绍

引用计数可以有效地管理对象生命周期:

1.2 引用计数的作用

1.1中的示例,反映不出引用计数真正的用处,因为示例中的对象的生命周期只是在一个函数中,而在函数内使用一个临时的对象,通常用不上它的引用计数,只要函数返回前将该对象销毁即可。应用计数真正排上用场的场景是在面向对象的程序设计架构中:用于对象直接传递和共享数据。

image.png

假如对象A生成了一个属性对象M,需要调用对象B的某一个方法,将对象M作为参数传递过去。对象M的释放原则如下:

总结:

引用计数可以很好的解决上述问题,在参数M的传递过程中,哪些对象需要使用这个对象,就把它的引用计数+1,使用完了后引用计数-1。
所有对象都遵守这个规则的话,对象的生命周期管理就可以完全交给引用计数了。我们也可以很方便地享受到共享对象带来的好处。

1.3 释放对象

image.png
//引用计数为1时,再次释放
[object release]; 
//这时候引用计数为0,但是retainCount不会置0。在上图中,程序异常崩溃了
NSLog(@"Reference Count = %lu", (unsigned long)[object retainCount]);

当引用计数为0时,对象的内存会被回收,而我们向一个已经被回收的对象发了一个retainCount消息,它的输出结果应该是不确定的,如果该对象的所占的内存被复用了,那么就有可能造成程序异常崩溃。
当最后一次执行release,系统知道马上就要回收内存了,就没有必要将retainCount减少1了,因为不管减不减,该对象都肯定会被回收,它的所有的内存区域,包括retainCount值也变得没有意义。不将这个值从1变成0,可以减少一次内存的操作,加速对象的回收。
例如Linux文件系统举例,Linux文件系统下删除一个文件,也不是真正地在文件的磁盘区域进行抹除操作,而只是删除该文件的索引节点号。与引用计数的内存回收方式类似,即回收时只做标记,并不抹除相关的数据。

补充:

当对象的引用计数变成0的时候,系统会调用delloc方法去销毁对象。我们可以在delloc方法中去销毁之前引用对象的指针,以及取消已经订阅的KVO,通知等。

1、调用 -release :引用计数变为零
* 对象正在被销毁,生命周期即将结束.
* 不能再有新的 __weak 弱引用, 否则将指向 nil.
* 调用 [self dealloc]
2、父类 调用 -dealloc
* 继承关系中最底层的父类 在调用 -dealloc
* 如果是 MRC 代码 则会手动释放实例变量们(iVars)
* 继承关系中每一层的父类 都在调用 -dealloc
3、NSObject 调 -dealloc
* 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法
4、调用 object_dispose()
* 为 C++ 的实例变量们(iVars)调用 destructors
* 为 ARC 状态下的 实例变量们(iVars) 调用 -release
* 解除所有使用 runtime Associate方法关联的对象
* 解除所有 __weak 引用
* 调用 free()

1.4 循环引用及检测

引用计数这种管理内存的方式虽然很简单,但是有一个比较大的瑕疵,即它不能很好地解决循环引用问题。


image.png

当前对象A和对象B的引用计数都是1,如果想要将A和B销毁,必须置引用计数为0,在没有程序员介入的情况下,对象A要释放,其引用计数必须减一,这样其成员变量B的引用计数就会减一,但是对象A的引用计数要减一,必须让对象B销毁,即B的引用计数减一才可以,这样对象A和B都依赖于对方,都在等待对方去释放。

image.png

如上图所示,多个对象间,依次引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减1,即依次持有,形成一个环状,在真实的编程环境中,环越大就越难发现。

解决方案:

1、主动断开
主动断开循环引用依赖于程序员自己手工显示地控制,相当于回到“谁申请谁释放”的手动内存管理,同时也依赖于程序员的能力,发现循环引用并在合适的时机断开循环引用回收内存。
2、弱引用
弱引用,虽然持有对象,但并不增加引用计数。比如:delegate。

检测循环引用

Xcode的Instruments工具集: image.png 点击Profile后,Xcode会打开模拟器,并安装程序到模拟器,但是不会运行程序,然后在打开Instruments工具集: image.png
选择Leaks,然后点击Choose: image.png 这个时候检测面板会弹出,此时还没有开始检测,鼠标悬停在左上角红色按钮上时,显示:Start an immediate mode recoding。点击按钮开始检测并记录,这时程序会被显示运行: image.png

上图中,已经检测到了一次内存泄漏,选中这次内存泄漏,下面详情区域切到Cycles & Roots:


image.png

使用Instrument显示设备离线(device offline)解决方案:
关闭Instruments,然后Xcode依次进行:清理(shift+command+K)+编译(command+B)+运行(command+I)

2、使用ARC

非ARC的应用,迁移到ARC,有一些迁移成本,但是Xcode专门集成了迁移工具,成本已经非常小了,而且,为了兼容第三方的非ARC开源库,你也可以在工程中随意使用编译参数-fno-objc-arc,这个参数允许对部分文件关闭ARC:


image.png

虽然ARC是与IOS5一同推出的,但是由于ARC的实现机制是在编译期完成的,所以使用ARC之后应用仍然可以支持iOS4.3。稍微需要注意的是,如果要在ARC开启的情况下支持IOS4.3,需要将weak关键字换成__unsafe_unretained。

Core Foundation对象

    CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, "hello world", kCFStringEncodingUTF8);
//CF对象需要手动管理
    CFRetain(str);
    CFRelease(str);

在ARC下,我们有时需要将一个Core Foundation对象转换成一个OC对象:

上一篇 下一篇

猜你喜欢

热点阅读