iOS开发——ARC

自动引用计数(ARC)是iOS开发中的内存管理技术,它自动管理对象的生命周期,降低了内存泄漏的风险。文章详细介绍了ARC的四个法则,__strong、__weak、__unsafe_unretained和__autoreleasing修饰符的使用,以及如何解决循环引用问题。此外,还阐述了ARC的规则,包括不能使用retain/release等方法,以及遵守内存管理的方法命名规则。最后,讨论了__bridge属性和属性关键字与所有权修饰符的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是自动引用计数

顾名思义,自动引用计数(ARC)是指内存管理中对引用采取自动计数的技术。我们知道以前的程序员关于内存管理是手动管理的也就是MRC,在后来才引入了ARC。那么关于ARC相比于MRC最大的优化是什么呢?总结一下就是:在LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者是release代码这样就会在降低程序崩溃、内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。如此一来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。

内存管理/引用计数

内存管理的四个法则

  1. 自己生成的对象,自己持有。
  2. 非自己生成的对象,自己也能持有。
  3. 不再需要自己持有的对象时释放。
  4. 非自己持有的对象无法释放。

对象操作与OC方法的对应:

在这里插入图片描述

__strong修饰符

__strong修饰符是id类型和对象类型默认的所有权修饰符。 以下源代码中的id变量,实际上被附加了所有权修饰符:

id obj = [[NSObject alloc] init];

id和对象类型在没有明确指定所有权修饰符时,默认为__strong修饰符。上面的源代码与以下相同:

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

附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会是放弃被赋予的对象。__strong修饰符便是对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

__strong修饰符的变量之间相互赋值

id __strong obj0 = [[NSObject alloc] init];//生成对象A			
id __strong obj1 = [[NSObject alloc] init];//生成对象B		
id __strong obj2 = nil;
obj0 = obj1;//obj0强引用对象B;而对象A不再被ojb0引用,被废弃
obj2 = obj0;//obj2强引用对象B(现在obj0,ojb1,obj2都强引用对象B)	
obj1 = nil;//obj1不再强引用对象B	
obj0 = nil;//obj0不再强引用对象B	
obj2 = nil;//obj2不再强引用对象B,不再有任何强引用引用对象B,对象B被废弃

通过上面的代码我们可以发现,__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者。赋值的本质是强引用的转变

关于__strong导致的循环引用的问题

举个例子:
Test类中有一个强引用类型的成员变量obj_,同时设置其set方法

@interface Test : NSObject {
    id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end

#import "Test.h"

@implementation Test
- (id)init {
    self = [super init];
    return self;
}

- (void)setObject:(id __strong)obj {
    obj_ = obj;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id test0 = [[Test alloc] init];//生成TestA
        id test1 = [[Test alloc] init];//生成TestB
        [test0 setObject:test1];
        [test1 setObject:test0];
        //NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)test0));
    }
    return 0;
}

可以看到我们声明了两个对象test0和test1,同时其内部含有自己的obj_,由于set方法同时给两个对象的成员变量分别赋值另一个对象所持有的TestA和TestB对象,所以现在的情况就是:

test0持有TestA
test1.obj也持有TestA
test1持有TestB
test0.obj也持有TestB

失效阶段的表示
在这里插入图片描述
testA 不能dealloc 因为test1.obj还持有testA
想要废除test1.obj就是要遵行成员变量的生命周期是与对象同步的这个观点,所以我们需要废除testB

而对于testB来说也是这样,想废除test0.obj就是要废除testA

因为都是强引用,所以就造成了谁也不愿意让步,所以造成了循环

循环引用有什么隐患?循环引用容易发生内存泄漏。所谓内存泄漏就是应当废弃的对象超出其生存周期后继续存在。

__weak修饰符

我们刚刚提到了使用__strong修饰符会造成循环引用的问题,那么如何才能避免循环引用呢?有__strong就说明肯定有__weak,也许你不用知道OC为什么会引入__weak修饰符,但你要知道使用__weak修饰符可以避免循环引用。

__strong修饰符是强引用,那么__weak修饰符就提供了弱引用。弱引用是什么意思呢,你可以理解为它因为弱所以不能持有对象的实例

我们看一下下面的代码:

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

我们编译一下就会发现,编译器会发出警告:
在这里插入图片描述

这是编译器在告诉你,此源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量obj。即变量obj持有对持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。那么如何才能使编译器不发出警告呢,我们将代码改成以下的形式:

id __strong obj0 = [[NSObject alloc] init];
id __weak obj1 = obj0;

因为带__weak修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果像下面这样将先前可能发生循环引用的类成员变量改成 附有__weak修饰符的成员变量的话,就可以避免循环引用。

@interface Test : NSObject
{
	id __weak obj_;
}
- (void)setObject:(id__strong)obj;
@end

__weak修饰符还有另一优点。在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)。

通过检查附有weak修饰符的变量是否为nil,可以判断被赋值对象是否已废弃。
遗憾的是,weak修饰符只能用于iOS5以上及OS X Lion以上版本的应用程序。在iOS4以及OS X Snow Leopard的应用程序中可使用__unsafe _unretained修饰符来代替。

__unsafe _unretained修饰符

__unsafe _unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有__unsafe _unretained修饰符的变量不属于编译器的内存管理对象。这一点在使用时要注意。

weak 修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,自动置为 nil。 _Unsafe_Unretain 不会置为 nil,容易出现 悬垂指针,发生崩溃。但是 _Unsafe_Unretain 比 __weak 效率高。
悬垂指针 指针指向的内存已经被释放了,但是指针还存在,这就是一个 悬垂指针 或者说 迷途指针。野指针,没有进行初始化的指针,其实都是 野指针

附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,生成的对象会立即释放。

在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在,如果不存在,那么程序就会崩溃。

__autoreleasing修饰符

在上一节内容iOS开发——MRC(手动内存管理)我解释了关于MRC中的autorelease和NSAutoreleasePool等知识,这些也是MRC情况下的方法,但是在实际情况下,ARC有效时autorelease功能也是起作用的,如图所示:
在这里插入图片描述

__autoreleasing修饰符自动调用

编译器在编译时会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。
以下情况即使不使用__autoreleaseing修饰符也能使对象注册到autoreleasepool。

+ (id) array {
	return [[NSMutableArray alloc]init];
}

如下:
+ (id) array {
	id obj = [[NSMutableArray alloc]init];
	return obj;
}

以下为使用__weak修饰符的例子。虽然__weak修饰符是为了避免循环引用而使用的,但在访问附有__weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。

id __weak obj1 = obj0;
NSLog(@"class = %@", [obj class]);

以下源代码与此相同。

id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@", [tmp class]);

那么为什么访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象呢?
这是因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此,访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象。

最后一个可非显示地使用__autoreleasing修饰符的例子,同前面讲述的id obj和id __storng obj完全一样。那么id的指针id *obj又如何呢?可以由id __strong obj的例子类推出id __strong *obj吗?其实,推出来的是id __autoreleasing *obj。同样地,对象的指针NSObject **obj便成为了NSObject *__autoreleasing *obj。

像这样,id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。

ARC规则

  1. 在ARC环境下编译源代码,必须要遵守一定的规则。具体ARC规则如下:
  2. 不能使用retain/release/retainCount/autorelease
  3. 不能使用NSAllocateObject/NSDeallocateObject
  4. 必须遵守内存管理的方法名规则
  5. 不要显式调用dealloc
  6. 使用@autorelease块代替NSAutoreleasePool
  7. 不能使用区域(NSZone)
  8. 对象型变量不能作为C语言结构体的成员
  9. 显式转换id和void*

不能使用 retain/release/retainCount/autorelease

设置ARC有效时,无需再次键入retain或release代码

前面我们介绍了retain/release/retainCount/autorelease这些方法是MRC里使用的,在我们设置ARC有效时,再使用的话编译器就会报错,所以总结一下:设置ARC有效时,禁止再次键入retain或者是release代码。

不能使用NSAllocateObject/NSDeallocateObject

一般情况下,通过调用NSObject类的alloc类方法来生成并持有OC对象:

id obj = [NSObject alloc];

但是就如GNUstep的alloc实现所示,实际上是通过直接点用NSAllocateObject函数来生成并持有对象的。
在ARC有效时,禁止使用NSAllocateObject函数。同retain等方法一样,如果使用会报错,所以禁止使用。

须遵守内存管理的方法命名规则

在ARC无效时,用于对象生成/持有的方法必须遵守以下的命名规则。

  1. alloc
  2. new
  3. copy
  4. mutableCopy

关于init
以init开始的方法的规则要比alloc/new/copy/mutableCopy更严格。该方法必须是实例方法,并且必须要返回对象。返回的对象应为id类型或该方法声明类的对象类型,返回的对象并不注册到autoreleasepool上。基本桑拿只是对alloc方法返回值的对象进行初始化处理并返回该对象。

不能显示调用dealloc

dealloc无法释放不属于该对象的一些东西,需要我们重写时加上去,例如:

  1. 通知的观察者,或KVO的观察者
  2. 对象强委托/引用的解除(例如XMPPMannerger的delegateQueue)
  3. 做一些其他的注销之类的操作(关闭程序运行期间没有关闭的资源)

__bridge

在这里插入图片描述

属性

属性关键字与所有权修饰符

在这里插入图片描述

ARC的实现

__strong修饰符

表示引用为强引用。在ARC模式下,只要一个变量有__strong标识了,就标识拥有了赋值的对象,不管赋值的对象是怎么来的。也可理解为只要见到__strong标识,编译器就会给那些不是你持有的对象自动加上retain,并且在变量超出作用域后自动调用了一次release。

那么赋值给附有__strong修饰符的变量在实际的程序中到底是怎样运行的呢?

//编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

如原源代码所示,两次调用objc_msgSend方法(alloc方法和init方法),变量作用域结束时通过objc_release释放对象。虽然ARC不能使用release方法,但由此可知编译器自动插入了release。

像该源代码这样,返回注册到 autoreleasepool 中对象的方法使用了objc_autoreleaseReturnValue 函数返回注册到autoreleasepool中的对象。但是 objc_autoreleaseReturnValue函数同 objc_autorelease 函数不同般不仅限于注册对象到autoreleasepool中。
objc_autoreleaseReturnValue 两数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调国方在调用了方法或函数后紧接着调用 objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue 函数与 objc_retain 函数不同,它即便不注册到 autoreleasepool中而返回对象,也能够正确地获取对象。通过objc_autoreleaseReturnValue 函数和objc_retainAutoreleasedReturnValue 函数的协作,可以不将对象注册到autoreleasepool中而直按传递,这一过程达到了最优化。如图所示:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值