【iOS】引用计数与autorelease


前言

最近在学习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

接下来我们详细了解一下各个思考方式:

  1. 自己生成的对象,自己持有

使用alloc,new,copy,mutablecopy名称开头的方法意味着自己生成的对象只能自己持有
我们这里的自己对应着对象的使用环境,也可以说是编程人员自身

id obj = [[NSObject alloc] init];
id obj = [NSObject new];

例如上述两种方法是完全一致的,我们使用alloc方法创建对象意味着自己生成的对象自己能够持有。

另外使用对这些名称使用驼峰命名法我们也可以自己生成并持有对象,例如:

  • allocMyObject
  • newThatObject
  • copyThis
  • mutableCopyYourObject
  1. 非自己生成的对象,自己也能持有

我们通过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以外
的方法取得的对象非自己生成并持有。

  1. 不再需要自己持有的对象时释放

自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法。
例如:

//自己生成并持有对象
id obj = [[NSObject alloc] init];

//释放对象
[obj release];

这样一来我们的对象的引用计数为0,对象已经被释放,我们的obj无法再次访问我们的对象,如果访问会导致程序崩溃

同样,自己生成而非自己所持有的对象,若使用retain方法持有,同样也可以使用release释放。

  1. 无法释放非自己持有的对象
//自己生成并持有对象
id obj = [[NSObject alloc] init];

//自己持有对象

[obj release];

//对象已释放 

[obj release];
/*
释放之后再次释放已非自己持有的对象!
应用程序崩溃!

崩溃情况:
再度废弃已经废弃了的对象时崩溃
访问已经废弃的对象时崩溃
*/

在第一次release之后我们的对象已经被释放,如果再次释放会导致程序崩溃

四、release与autorelease

release是一个立即释放对象的方法。当你调用release时,对象的引用计数会立即减少,如果引用计数达到0,对象会被立即释放。因此,release需要在不再需要对象时手动调用,以避免内存泄漏。

autorelease是一种延迟释放对象的机制。当一个对象被autorelease时,它的引用计数不会立即减少,而是在当前autorelease pool被销毁时才会减少。
这意味着,即使你不再使用一个对象,它也不会立即被释放,而是等待当前Runloop结束时才会被释放。因此,autorelease可以在需要延迟释放对象时使用,以避免频繁释放和创建对象带来的性能开销。

这里需要注意的是我们只有在MRC中才可以使用releaseautorelease对我们的对象进行内存管理。
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的引用计数。

猜你喜欢

转载自blog.csdn.net/weixin_72437555/article/details/133131802