最近在学习iOS内存管理之引用计数,特此撰写博客记录学习过程
自动引用计数(ARC)是指内存管理中对引用计数采取自动计数的方式
在Objective-C中采用Automatic Reference Counting
(ARC)机制,让编译器来进行内存管理。在新一代Apple
LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,这在降低程序崩溃、内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象。如此来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。
这些优点无疑极具吸引力,但关于ARC技术,最重要的还是下面这一点:
“在LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者是release代码。”
iOS中的引用计数是一种内存管理机制,用于追踪和管理内存的使用情况。每个对象都有一个引用计数器,用于记录有多少个指针指向该对象。
当对象被创建时,其引用计数器会被设置为1。当一个指针指向该对象时,引用计数器会增加1。如果有一个指针不再指向该对象,那么引用计数器会减少1。当引用计数器为0时,系统会自动释放对象的内存空间。
我们以办公室开灯关灯为例距离说明我们OC的引用计数:
此时的对象相当于我们办公室的照明设备
我们以一张图来理解我们引用计数的内存管理:
我们以开灯关灯为例子简单地了解了我们内存管理的机制,那么在OC中我们该如何进行我们的内存管理呢?
对象操作与OC方法的对应
对象操作 | Objective-C方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy等 |
持有对象 | retain |
释放对象 | release |
废弃对象 | dealloc |
接下来我们详细了解一下各个思考方式:
使用alloc,new,copy,mutablecopy名称开头的方法意味着自己生成的对象只能自己持有
我们这里的自己对应着对象的使用环境,也可以说是编程人员自身
id obj = [[NSObject alloc] init];
id obj = [NSObject new];
例如上述两种方法是完全一致的,我们使用alloc方法创建对象意味着自己生成的对象自己能够持有。
另外使用对这些名称使用驼峰命名法我们也可以自己生成并持有对象,例如:
我们通过alloc,new,copy,mutablecopy
以外
的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者
但是我们可以通过retain
方法成为对象的持有者
//获取非自己生成并持有的对象
id obj = [NSMutableArray array];
//获取到了对象,但是自己并不持有
//自己持有对象
[obj retain];
从上述的举例可以看出,通过retain
方法,非自己持有生成的对象跟用alloc/new/copy/mutableCopy
方法生成并持有的对象一样,成了自己所持有的。
我们在上文说到我们通过alloc/new/copy/mutableCopy
以外方法取得的对象存在,但自己不持有对象,是如何实现的呢?
解释:
这是因为它们在被创建时并没有任何对象对它们进行retain
。同时在对象被创建之后对对象使用了autorelease方法
我们以以下代码为例阐述我们非自己生成并持有的对象生成的过程
- (id)object {
id obj = [[NSObject alloc] init];
[obj autorelease];
return obj;
}
通过查看NSMutableArray的这些类方法的实现,你会发现它们内部都调用了 autorelease
方法。
通过alloc等之外方法创建的对象在创建时会被自动加入到当前的autorelease pool
中。Autorelease pool
是一个特殊的对象,它持有一组对象,并会在合适的时候释放这些对象。这就是为什么我们通过alloc,new,copy,mutablecopy
以外
的方法取得的对象非自己生成并持有。
自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release
方法。
例如:
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//释放对象
[obj release];
这样一来我们的对象的引用计数为0,对象已经被释放,我们的obj无法再次访问我们的对象,如果访问会导致程序崩溃
同样,自己生成而非自己所持有的对象,若使用retain
方法持有,同样也可以使用release
释放。
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
[obj release];
//对象已释放
[obj release];
/*
释放之后再次释放已非自己持有的对象!
应用程序崩溃!
崩溃情况:
再度废弃已经废弃了的对象时崩溃
访问已经废弃的对象时崩溃
*/
在第一次release之后我们的对象已经被释放,如果再次释放会导致程序崩溃
release
是一个立即释放对象的方法。当你调用release
时,对象的引用计数会立即减少,如果引用计数达到0,对象会被立即释放。因此,release
需要在不再需要对象时手动调用,以避免内存泄漏。
而autorelease
是一种延迟释放对象的机制。当一个对象被autorelease
时,它的引用计数不会立即减少,而是在当前autorelease pool
被销毁时才会减少。
这意味着,即使你不再使用一个对象,它也不会立即被释放,而是等待当前Runloop结束时才会被释放。因此,autorelease
可以在需要延迟释放对象时使用,以避免频繁释放和创建对象带来的性能开销。
这里需要注意的是我们只有在MRC中才可以使用release
与autorelease
对我们的对象进行内存管理。
在ARC环境中我们使用release
方法会报错,因为我们的编译器自动帮我们进行了内存管理
同时在《Objective-C高级编程-iOS与OS+X多线程和内存管理》中说道:我们使用autorelease
方法时,可以使取得的对象存在,但自己不持有对象。这样的功能可以使对象超出指定生存范围时能够自动并正确地释放,如下图所示:
对于release
方法我们这里不再讲述,我们主要讲一下autorelease
方法
autorelease
方法听起来很像ARC,但其实它更像c语言中的局部变量,当我们的局部变量超出其作用域时,我们的局部变量会被自动废弃
autorelease像c语言中对待局部变量一样来对待对象,当对象超出作用域,对象实例的release方法会被自动调用
与C语言中的局部变量不同的是,我们可以设定我们变量的作用域
调用autorelease的对象,在废弃NSAutoreleasePool对象时,都将会调用release方法,用源码表示如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
NSLog(@"%lu", [obj retainCount]);
这段代码执行下来,我们的obj
会在pool drain
之后自动销毁,之后我们就无法再次访问obj对象
当然在使用autorelease方法时还会有另外一种情况:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [NSMutableArray array];
[obj autorelease];
[pool drain];
NSLog(@"%lu", [obj retainCount]);
如果我们将alloc
方法替换为array
方法时,我们的这段程序会报错,这是因为我们的array
方法内部已经自动帮我们生成了一个NSAutoreleasePool
对象,当我们再次手动创建一个NSAutoreleasePool
对象时并进行[pool drain];
操作时,手动创建的NSAutoreleasePool对象与自动创建NSAutoreleasePool的对象都会将obj加入释放池的数组中,因此对我们手动创建的pool
对象进行销毁操作时也意味着我们array
方法内部的自动释放池被销毁,obj
的引用计数为0,被自动销毁
我们代码中常用的赋值方式有两种,一种是直接赋值,一直是通过setter方法进行赋值。
我们给出代码例子:
a.room = r;//setter
id obj1 = obj;//直接
a是一个对象,并且r是对另一个对象的引用,那么这段代码实际上是在将a对象的room属性设置为r引用的对象。
在这种情况下,如果a对象的r属性原来持有一个不同的对象,那么那个对象的引用计数会减1,而新的对象的引用计数会加1。这是因为当你将一个对象的属性设置为另一个对象时,你实际上是在增加对新对象的引用,同时减少对原对象的引用。
如果room属性原来没有持有对象,那么仅仅会对新的对象的引用计数加1。
preson *a = [[preson alloc] init];
preson *b = [[preson alloc] init];
room *r = [[room alloc] init];
r.no = 888;
a.room = r;//是新对象引用计数加1
b.room= r;
NSLog(@"%lu", [r retainCount]);
对于id obj1 = obj;
,这段代码只是创建了一个新的引用obj1
,它引用的是obj
对象。这个操作并没有改变obj
的引用计数,因为obj1
只是对obj
的一个新引用,并没有增加或减少obj
的引用计数。