上一篇说了ARC无效时也就是MRC的内存管理规则,在ARC有效时,引用计数式内存管理的本质部分并没有改变,ARC只是自动帮助我们处理引用计数的相关部分,我们无需再键入retain 、 release 、autorelease,而是对id类型和对象类型附加所有权修饰符来记述。所有权修饰符共有四种,下面一一介绍、、、
1. __strong修饰符
① id类型和对象类型的默认所有权修饰符。
id obj = [[NSObject alloc]init]; //该句源代码与以下这句相同
id __strong obj = [[NSObject alloc]init];
② 表示对于对象的强引用,当变量在超出其变量作用域时,强引用失效,该变量所引用的对象会被释放。
③ 对于非自己生成的对象, 使用__strong修饰符的变量也会持有该对象
NSArray __strong *arr = [NSArray array];
/*
变量arr使用__strong修饰符修饰,持有该非自己生成的对象
*/
④ __strong修饰符修饰的变量可以相互赋值
id obj0 = [[NSObject alloc]init]; //obj0持有对象A的强引用
id obj1 = [[NSObject alloc]init]; //obj1持有对象B的强引用
id obj2 = nil; //obj2不持有任何对象
obj2 = obj0; //obj2持有对象A的强引用
obj1 = obj2; //obj1持有对象A的强引用
//此时对象B没有持有者,引用计数为0,被废弃。
//对象A的持有者为obj0,obj1,obj2,故引用计数为3
obj0 = nil;
obj1 = nil;
obj2 = nil;
//此时对象A没有持有者,引用计数为0,被废弃
在使用__strong修饰符修饰的变量相互赋值时,有时会出现循环引用,也就是互相引用的问题。
先看一个互相引用的栗子
@interface Test : NSObject
{
id _obj;
}
- (void)setObject:(id)obj;
@end
@implementation Test
- (void)setObject:(id)obj{
_obj = obj;
}
@end
{
Test *t1 = [[Test alloc]init];//t1持有对象A的强引用
Test *t2 = [[Test alloc]init];//t2持有对象B的强引用
[t1 setObject:t2];//对象A的_obj成员变量持有对象B的强引用
[t2 setObject:t1];//对象B的_obj成员变量持有对象A的强引用
}
/*
t1超出作用域,强引用失效,释放对于对象A的引用
t2超出作用域,强引用失效,释放对于对象B的引用
此时,对象A的_obj成员变量持有对象B的强引用,对象B的_obj成员变量持有对象A的强引用
二者互相引用,无法释放,发生内存泄漏
*/
还有一个自身强引用的栗子
Test *t1 = [[Test alloc]init];
[t1 setObject:t1];
像这样,循环引用容易发生内存泄漏, 所谓内存泄漏指应当被废弃的对象在超出其生存期后继续存在。下面说的
__weak修饰符可以解决这个问题。
2.__weak修饰符
提供对象的弱引用,不持有对象,在超出变量作用域时,引用的对象即被释放。当持有某对象的弱引用时,该对象被废弃,则此弱引用自动失效切被赋值为nil。
id __weak obj1 = nil;
{
id obj0 = [[NSObject alloc]init];//obj0持有对象A的强引用
obj1 = obj0;
//obj0持有对象A的弱引用,也就是不持有对象A
NSLog(@"被赋值 obj1:%@",obj1);
}
//变量obj0超出作用域,强引用失效,释放自己持有的对象,对象A没有持有者,被废弃
//由于对象A被废弃,obj1的弱引用自动失效,且被赋值为nil(空弱引用)
NSLog(@"对象A被废弃 obj1:%@",obj1);
前面已经说了,使用__weak修饰符可以解决互相引用的问题,那么对于前面说到的问题解决方法如下:
@interface Test : NSObject
{
id __weak _obj;
}
- (void)setObject:(id)obj;
@end
__weak修饰符不持有对象,如果把自己生成并持有的对象赋值给__weak修饰的变量会怎样呢?
在此例中,由alloc方法生成的实例变量是自己生成并持有,但是__weak修饰的变量不持有对象,所以生成的对象会立即被释放,故编译器给出警告。
3.__unsafe_unretained修饰符
不安全的所有权修饰符,所修饰的变量不属于编译器的内存管理对象,既不持有对象的强引用也不持有弱引用。使用该修饰符时要确保被赋值的对象确实存在,使用该修饰符的变量在引用的对象释放后不会被赋值为nil。
id __unsafe_unretained obj2 = nil;
{
id obj = [[NSObject alloc]init];//obj持有对象A的强引用
obj2 = obj;//obj2对于对象A既不持有强引用也不持有弱引用
NSLog(@"A: %@",obj2);//输出obj2变量所表示的对象
}
//此时,obj超出作用域,释放持有的对象,对象A没有持有者,被废弃
NSLog(@"B: %@",obj2);
//输出obj2所表示的对象,但是对象已经被废弃,所以是错误访问故可能在运行过程中崩溃
4.__autoreleasing修饰符
与MRC下的autorelease方法对应,使用该修饰符修饰的对象会被自动注册到autoreleasepool中。在ARC下,不能使用autorelease方法,也不能使用NSAutoreleasePool类,但是可以使用@autoreleasepool{}块来代替NSAutoreleasePool的作用,使用__autorelease修饰符来代替autorelease方法。
一般来说显式的添加__autorelease修饰就和显式的添加__strong修饰符一样罕见。几种情况说明如下:1️⃣ 不是以alloc、new、copy、mutableCopy开头的方法,其返回值的对象默认注册到autoreleasepool中
@autoreleasepool {
id obj = [NSArray array];
//obj为强引用,所以取得非自己生成的对象并持有
//该对象由编译器判断方法名后自动注册到autoreleasepool中
}
//obj超出作用域,强引用失效,自动释放所持有的对象
//@autoreleasepool块结束,注册到autoreleasepool中的所有对象被自动释放
//对象的所有者不存在,被废弃
2️⃣访问__weak修饰的变量时,该对象默认注册到autoreleasepool中。因为__weak修饰符修饰的变量只持有对象的弱引用,在访问的过程中很可能该对象已经被废弃。将该对象注册到@autoreleasepool中可以保证在@autoreleasepool块结束前该对象不会被释放
3️⃣id类型的指针或者对象类型的指针,没有显式指定时会被加上
id *obj <===> id __autoreleasing *obj
NSObject **obj <===> NSObject * __autoreleasing *obj;
四个所有权修饰符就说完啦~接下来说的是ARC的具体规则
1️⃣ 不能使用 retain、release、retainCount、autorelease 方法,这几种方法只能在ARC无效且手动管理内存时使用
2️⃣ 不能使用NSAllocateObject、NADealoocateObject,ARC下一般通过NSObject类的alloc类方法来生成并持有对象
3️⃣ 遵守内存管理的方法命名规则:以alloc / new / copy / mutableCopy开头的方法必须返回给调用方所应当持有的对象;以init开始的方法必须是实例方法,且必须返回对象,类型为id类型或该方法声明类的对象类型或该类的超类型或子类型,此返回对象不注册到autoreleasepool中,只是对alloc方法返回值的对象进行初始化处理且返回该对象
4️⃣ 不能显示调用dealloc。无论ARC是否有效,只要对象没有持有者,该对象就会被废弃,调用对象的dealloc方法
5️⃣ 使用@autoreleasepool块代替NSAutoreleasePool类
6️⃣ 不能使用区域(NSZone)。不管ARC是否有效,NSZone在现在的运行时系统中已被单纯的忽略
7️⃣ 对象类型不能作为C语言结构体的成员。因为ARC由编译器管理内存,所以编译器必须要知道并管理对象的生存周期,但是C语言没有方法来管理结构体成员的生存周期,故这在标准上就是不可实现的。对象型变量要想假如结构体成员时,可以强制转换成void*或者使用__unsafe_unretained修饰符修饰。
struct Data{
NSArray __unsafe_unretained *array;
};
8️⃣ 不能显示转换void*和 id类型。在ARC无效时,显式转换二者不会出现问题,但在ARC中会出现编译错误,在ARC下可以通过 __bridge 来实现相互转换。__bridge还有两种转换,分别是__bridge_retained和__bridge_transfer。__brigde_retained转换可以使转换赋值的变量持有所赋值的对象,__bridge_transfer使被转换的变量所持有的对象在变量被赋值给转换目标后释放。
id obj = [[NSObject alloc]init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
void *p1 = (__bridge_retained void *)o;
id o1 = (__bridge_transfer id)p1;
最后一个要说的是,属性声明中的属性和所有权修饰符对应的关系
如果对您有所帮助,请记得点赞哦~谢谢~