目录
ARC规则
一、概要
二、内存管理的思考方式
引用计数式内存管理的思考方式就是思考ARC所引起的变化。
- 自己生成的对象,自己所持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 无法释放非自己持有的对象
三、所有权修饰符
所谓对象类型就是指向NSObject这样的Objective-C类的指针,例如NSObject * ; id类型用于隐藏对象类型的类名部分,相当于C语言中常用的 void * ;
ARC有效时,id类型和对象类型,必须附加所有权修饰符。
- __strong修饰符
默认所有权修饰符
//默认所有权修饰符为 __strong 相当于下面代码
id objc = [[NSObject alloc] init];
//__strong 修饰符
id __strong obj = [[NSObject alloc] init];
在特定作用域内的__strong
{
//自己生成并持有对象
//因为变量为强引用,所以自己持有对象
id __strong obj = [[NSObject alloc] init];
}
//obj超出作用域,自动释放持有的对象
//对象的持有者不存在,因此废弃该对象
ARC无效时,该源代码可记述如下
{
//创建并持有对象
id obj = [[NSObject alloc] init];
//在obj超出作用域被废弃之前释放掉持有对象
[obj release];
}
赋值操作时的__strong
id __strong obj0 = [[NSObject alloc] init];//obj0持有对象A
id __strong obj1 = [[NSObject alloc] init];//obj1持有对象B
//obj0持有B对象
obj0 = obj1;
//此时obj0不再持有A对象,A对象释放
//B对象被obj0和obj1持有
类成员变量使用 __strong
Test类的声明与实现
@interface Test:NSObject{
id __strong obj_
}
@end
@implementation Test
-(id)init{
self = [super init];
return self;
}
-(void)setObject:(id __strong)obj{
obj_ = obj;
}
@end
下面试着使用该类
{
//test持有Test对象的强引用
id __strong test = [[Test alloc] init];
//Test对象的obj_成员持有NSObject对象的强引用
[test setObject:[[NSObject alloc] init]];
}
//test变量超出其作用域,自动调用[test release]
//test指向的对象的引用计数为0,调用[test delloc].
//test指向的对象销毁时,会先释放NSObjec对象,NSObject对象的引用计数也变为0,调用其[object delloc]
重要说明:
__strong __weak __ autoreleasing修饰符,可以保证将附有这些修饰符的自动变量初始化为nil;
id __strong obj0;
id __weak obj1;
id __autoreleasing obj2;
以下源代码与上相同
id __strong obj0 = nil;
id __weak obj1 = nil;
id __autoreleasing obj2 = nil;
自己生成的对象,自己所持有 和 非自己生成的对象,自己也能持有
通过对带__strong修饰符的变量赋值便可达成。
不再需要自己持有的对象时释放
通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所属对象废弃)或者对变量赋值。
无法释放非自己持有的对象
ARC下由于不必再次键入release,所以原本就不会执行。
- __weak修饰符
只有__strong会出现循环引用问题
场景一:循环引用
{
id test0 = [[Test alloc] init];//对象A
id test1 = [[Test alloc] init];//对象B
//持有对象B的有:test1,A对象->obj_; B的引用计数为2
[test0 setObject:test1];
//持有对象A的有:test0,B对象->obj_; A的引用计数为2
[test1 setObject:test0];
}
//超出test0、test1的作用域范围,test0、test1废弃,分别放弃其持有的对象A、B;
//A与B的引用计数都变为1。对象A没有销毁,B也没有销毁。
场景二:对自身的强引用
{
id test = [[Test alloc] init];//对象A 引用计数为1;
[test setObject:test];//对象A->obj_ 赋值,对象A的引用计数为2;
}
//离开作用域,变量test被废弃,
//对象A的引用计数变为1,
//对象A,无法释放
场景三:__weak 修饰变量
{
//obj0变量为强引用,所以自己持有对象
id __strong obj0 = [[NSObject alloc] init];//生成对象A,引用计数为1
//obj1变量持有生成对象的弱引用,不会导致引用计数改变
id __weak obj1 = obj0;//对象A引用计数为1
}
//obj0超出作用域,强引用失效,自动释放自己持有的对象 对象A引用计数为0
//因为对象的所有者不存在,所以废弃该对象。
避免循环引用
@interface Test:NSObject{
id __weak obj_;
}
-(void)setObject:(id __strong)obj;
@end;
自动失效且自动赋值nil;
//__weak修饰的变量,在持有对象被废弃,则此弱引用自动失效,且处于nil被赋值的状态。
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];//obj0持有对象A
obj1 = obj0;
NSLog(@"obj1=%@",obj1);//obj1=<NSObject: 0x1c0011560>
}
NSLog(@"obj1=%@",obj1);// obj1=(null)
像这样,使用__weak修饰符可避免循环引用。通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。
- __unsafe_unretained修饰符
该修饰符与__weak修饰符作用相同,区别是对象释放后不会对变量赋值nil,会导致内存访问出错。
//__weak修饰的变量,在持有对象被废弃,则此弱引用自动失效,且处于nil被赋值的状态。
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];//obj0持有对象A
obj1 = obj0;
NSLog(@"obj1=%@",obj1);//obj1=<NSObject: 0x1c0011560>
}
NSLog(@"obj1=%@",obj1);//崩溃 Thread 1: EXC_BAD_ACCESS (code=1, address=0xd2ba7beb8)
- __autoreleasing修饰符
在ARC下NSAutoreleasePool类不允许使用,对象的autorelease也不可调用。可通过以下方式来使用:
但是,显示的附加__autoreleasing修饰符很罕见,原因如下。
@autoreleasepool {
//编译器会判断方法名是否以alloc/new/copy/mutableCopy开始,
//如果不是,则自动将返回值的对象注册到autoreleasepool
//obj为强引用持有该对象,并且由编译器确定后自动注册到autoreleasepool
id __strong obj = [NSArray array];
}
//离开作用域,obj废弃,强引用失效
//@autoreleasepool块结束,注册到autoreleasepool中的对象自动释放
自动放入自动释放池的过程
+(id)array{
//默认__strong,强引用
id obj = [[NSArray alloc] init];
//该对象作为返回值自动放入autoreleasepool
return obj;
}
实际访问附有__weak修饰符的变量时,必定要访问注册到autoreleasepool的对象。
id __weak obj1 = obj0;
NSLog(@"class=%@",[obj1 class]);
以上代码与下面相同
id __weak obj1 = obj0;
id __autoreleasing temp = obj1;
NSLog(@"class=%@",[obj1 class]);
//为什么在访问附有__weak修饰的变量时必须注册到autoreleasepool对象呢?
//因为__weak修饰符只持有对象的弱引用,访问过程中有可能被废弃。
//如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。
非显示的使用__autoreleasing的另一个例子
id obj;
//与之等价的是
id __strong obj;
//或者
NSObject * __strong obj;
//那么与
id * obj;
//等价的是否是
id __strong * obj;
//答案是否定的,与之等价的是
id __autoreleasing * obj;
//或者
NSObject * __autoreleasing * obj;
//即
NSObject ** obj;
//默认的修饰符是
NSObject * __autoreleasing * obj;
//如下方法error的参数的类型是 NSError * __autoreleasing *;
[NSString stringWithContentsOfURL:(nonnull NSURL *)
encoding:(NSStringEncoding)
error:(NSError *__autoreleasing _Nullable * _Nullable)];
//这种方法调用时,如下
NSError * error = nil;
[NSString stringWithContentsOfURL:[NSURL URLWithString:@"url"]
encoding:NSUTF8StringEncoding
error:&error];
//error实例的生成是在方法内部,要想能在方法外部使用,需要在自动释放池注册
NSLog(@"error=%@",error);
对象指针类型赋值时,所有权修饰符必须一致
//编译报错
NSObject * obj = [[NSObject alloc] init];
NSObject ** op = &obj;
//编译通过
NSObject * obj = [[NSObject alloc] init];
NSObject * __strong * op = &obj;
//编译通过
NSObject * __weak obj = [[NSObject alloc] init];
NSObject * __weak * op = &obj;
//编译通过
NSObject * __unsafe_unretained obj = [[NSObject alloc] init];
NSObject * __unsafe_unretained * op = &obj;
//编译通过
NSObject * __autoreleasing obj = [[NSObject alloc] init];
NSObject * __autoreleasing * op = &obj;
详细了解下@autoreleasePool
//autoreleasepool可以嵌套使用
//autoreleasepool代码块同样适用于非ARC模式
@autoreleasepool {
@autoreleasepool {
}
}
//调用非公开函数,帮助我们调试注册到autoreleasepool上的对象
_objc_autoreleasePoolPrint();
四、规则
-
不能使用 retain/release/retainCount/autorelease
-
不能使用NSAllocateObject 和 NSDeallocateObject
-
须遵守内存管理的方法命名规则
-
不要显示调用dealloc
-
使用@autoreleasepool代替NSAutoreleasePool
-
不能使用区域NSZone
-
对象型变量不能作为C语言结构(struct/union)的成员
//编译器报错
struct Datas{
NSMutableArray*array;
};
//这样可以 __unsafe_unretained 不属于编译器内存管理对象
struct Datas2{
NSMutableArray* __unsafe_unretained array;
};
- 显示转换id和void *
id obj = [[NSObject alloc] init];
void* obj1 = (__bridge void *)obj;
void * obj2 = (__bridge_retained void*)obj;
id obj3 = (__bridge id)obj1;
id obj4 = (__bridge_transfer id)obj2;
五、属性
属性声明的属性 | 所有权修饰符 |
assign | __unsafe_unretained 修饰符 |
copy | __strong 修饰符(但是赋值的是被复制的对象) |
retain | __strong 修饰符 |
strong | __strong 修饰符 |
unsafe_unretained | __unsafe_unretained 修饰符 |
weak | __weak 修饰符 |
六、数组
数组释放时要保证数组内的元素释放,避免引起内存泄漏。 静态数组,在释放数组时会自动释放数组内的元素。 动态数组,在释放数组时需要手动将数组内的元素置为nil,然后释放数组。
但在iOS中释放动态数组时也会自动释放数组内的元素。
//retainCount 1
NSObject * obj = [[NSObject alloc] init];
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));
//retainCount 2
NSMutableArray * mArray = [[NSMutableArray alloc] init];
[mArray addObject:obj];
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));
//retainCount 1
mArray = nil;
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));
//retainCount 0
obj = nil;
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));
ARC的实现
…
行者常至,为者常成!