ARC规则
所有权修饰符
ARC有效时,id 类型和对象类型同 C 语言其他类型不同,类型上必须附加所有权修饰符:
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
- __strong 是 id 和对象类型默认的所有权修饰符,没明确规定时默认为此
{
id __strong obj = [[NSObject alloc] init];
}
//ARC无效
{
id obj = [[NSObject alloc] init];
[obj release];
}
//以上两个代码一样
//持有强引用的对象在超出其作用域时被废弃,引用的对象随之释放
取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//强引用所以持有
__strong 修饰的变量,不仅在变量作用域,在赋值上也能够正确管理对象所有者
在类成员变量中,也可以在方法参数上使用附有 __strong 修饰符的变量
__ strong,__ weak, __autoreleasing 可以保证将附有这些修饰符的自动变量初始化为 nil
- __weak 是为了解决循环引用引起的内存泄露,内存泄漏是应当废弃的对象在超出生存周期后继续存在(对自身的强引用也会发生循环引用
id __weak obj = [[NSObject alloc] init];
//警告,生产对象会被立即释放
id __strong obj0 = [[NSObject alloc] init];
id __weak obj1 = obj0
//可以
在持有某对象弱引用时,若该对象被废弃,则弱引用自动失效并处于 nil
- __ unsafe_unretained 是不安全的所有权修饰符,修饰的变量不属于编译器的内存管理对象
同 __weak 一样,无法持有生成对象,但持有对象废弃时不会归为 nil,再访问很可能崩溃,在 iOS4 及 OS X Snow Leopardde 应用程序中,必须用 unsafe 代替 weak,在通过 unsafe 修饰的变量使用其对象时,必须确保其确实存在
- __autoreleasing 当 ARC 有效时不能使用 autorelease 方法,也不能使用NSAutoreleasePool 类
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
用 @autoreleasepool 块来替代 NSAutoreleasePool 类对象生产持有及废弃
非显示使用 __autoreleasing 的情况:
取得非自己生成并持有的对象时,被注册到 autoreleasepool ,编译器会检查方法名是否以 alloc/new/copy/mutableCopy 开始,如果不是,对象作为函数的返回值将自动注册到 autoreleasepool
-
访问 __weak 变量时,必定要访问注册到 autoreleasepool 的对象
id __weak obj1 = obj0; NSLog(@"class=%@", [obj1 class]); //等同于 id __weak obj1 = obj0; id __autoreleasing tmp = obj1; NSLog(@"class=%@", [tmp class]);
在访问 __weak 变量引用的对象时,对象可能被废弃,把访问对象注册到autoreleasepool,那么在 @autoreleasepool 块结束前都能确保存在
-
id 指针 id *obj 和对象指针 NSObject **obj 在没有显示指定时会附加 __autoreleasing
等同于 id _autoreleasing *obj 和 NSObject * _autoreleasing *obj
NSError *error = nil; NSError **pError = &error; //编译错误 //赋值给对象指针时,所有权修饰符必须一致 NSError *error = nil; NSError *__strong *pError = &error; //编译正常
规则
-
不能使用 retain/release/retainCount/autorelease
内存管理是编译器的工作
不能使用 NSAllocateObject/NSDeallocateObject
需遵循内存管理的方法命名规则
alloc/new/copy/mutableCopy 开始命名的方法返回对象时,必须返回给调用方所应当持有的对象
init 开始的方法必须是实例方法,必须返回对象,应为 id 类型或方法声明类的对象类型,或该类的超类或子类,不注册到 autoreleasepool 上,只是对 alloc 方法返回值的对象进行初始化处理并返回。
-(void) initialize 不包含在上述规则里
- 不要显式调用 delloc
不管 ARC 是否有效,对象废弃时都会调用 delloc,有效时不必写 [super delloc]
使用 @autoreleasepool 代替
不能使用 NSZone
不管 ARC 是否有效,区域在现在的运行时系统都已被忽略
- 对象型变量不能作为 C 语言结构体的成员
因为 ARC 把内存管理的工作分配给编译器,所以编译器必须能知道并管理对象的生存周期,但 C 语言无法管理结构体成员的生存周期。要把对象加入结构体成员中,可强制转换为 void* 或附加 __unsafe_unretained (注意避免内存泄露)
- 显示转换 id 和 void*
/* ARC 无效 */
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];
//id 和 void* 相互转换没问题,但 ARC 有效时编译错误
/* ARC 有效 */
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
//id 或对象型赋给 void* 或者逆向赋值,都需要进行特定转换,如果只想单纯赋值可以用"__bridge 转换"
但转换为 void* 的 __ bridge 转换,安全性和 ___unsafe_unretained 相近,甚至更低
另两种 __ bridge 转换," __ bridge_retained 转换"和" __ bridge_transfer 转换"
//__bridge_retained 转换
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
//使转换赋值的变量也持有所赋值的对象\
/* ARC 无效 */
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
//__ bridge_transfer转换
id obj = (__bridge_transfer id)p;
//与 retained 相反,被转换的变量在持有对象被赋值给转换变量后随之释放
/* ARC 无效 */
id obj = (id)p;
[obj retain];
[(id)p release];
如果使用以上两种转换,那么不使用 id 型或对象型变量也可以生成持有及释放对象,虽然可以,但不推荐
这些转换多用在 Object-C 对象和 Core Foundation 对象之间
Core Foundation 对象和 Object-C 对象区别很小,只是由哪个框架(Core Foundation 框架还是 Foundation 框架)生成。无论是哪种框架生成的对象,一旦生成,就能在不同框架中使用。两者的转换不需要额外的 CPU 资源,因此称为“免费桥”(Toll-Free Bridge)
可以使用以下函数转换 Object-C 对象和 Core Foundation 对象
CFTypeRef CFBridgingRetain(id X){
return (__bridge_retained CFTypeRef)X;
}
id CFBridgingRelease(CFTypeRef X){
return (__bridge_transfer id)X;
}
/* Object-C 对象转为 Core Foundation 对象 */
CFMutableArrayRef cfObject = NULL;
{
id obj = [[NSMutableArray alloc] init];
cfObject = CFBridgingRetain(obj);
CFShow(cfObject);
printf("retain count = %d\n",CFGetRetainCount(cfObject));
//=2
}
printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject)); //=1
CFRelease(cfObject);
//也可以用 __ bridge_retained 转换替代 CFBridgingRetain
//用 __bridge 转换替代会造成悬垂指针
/*Core Foundation 对象转为 Object-C 对象 */
{
CFMutableArrayRef cfObject = CFArrayCreateMutable(kCFAlloctorDefault, 0, NULL);
prinft("retain count = %d\n",CFGetRetainCount(cfObject));
//=1
id obj = CFbridgingRelease(cfObject);
printf("retain count after the cast = %d\n", CFGetRetainCount(cfObject)); //=1
NSLog(@"class=%@",obj);
}
//可用 __bridge_transfer 转换替代 CFbridgingRelease
//用 __bridge 转换替代会造成内存泄漏
属性
ARC 有效时,Object-C 类的属性也会发生变化
@property (nonatomic, strong) NSString *name;
属性声明的属性 | 所有权修饰符 |
---|---|
assign | __unsafe_unretained |
copy | __strong(赋值的是被复制的对象) |
retain | __strong |
strong | __strong |
unsafe_unretained | __unsafe_unretained |
weak | __weak |
以上各属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量中。只有 copy 不是简单的复制,是通过 NSCopying 接口的copyWithZone: 方法复制赋值源所生成的对象
在声明类成员变量时,同属性声明中的属性不一致会引起编译错误
数组
//附有 __strong 修饰符的变量作为静态数组使用
id objs[10];
//__ weak, __ autoreleasing, __ unsafe_unretained 也相同
除 __ unsafe_unretained 以外的修饰符保证其指定的变量初始化为 nil
数组变量超出作用域时,数组中各个赋有 __strong 修饰符的变量也随之失效,对象也随之释放
动态数组时,会选择使用 NSMutableArray,NSMutableDictionary,NSMutableSet 等 Foundation 框架的容器,这些容器会恰当地持有追加的对象并管理
但也可以使用赋有 __strong 修饰符的变量声明动态数组,需要遵守一些事项
声明动态数组用指针
id __strong *array = nil;
因为“ id *类型” 默认为“ id __ autoreleasing *类型”,所以必须显式指定为strong,以及虽然保证了赋有 __ strong 的 id 型变量被初始化为 nil,但不保证赋有__strong 的 id 指针型变量初始化为 nil
使用类名时如下记述
NSObject * __strong *array = nil;
使用 calloc 函数确保想要分配的附有 __strong 修饰符变量的容量占有的内存块
//初始化
array = (id __strong *)calloc(entries, sizeof(id));
该代码分配了 entries 格所需内存块,由于使用赋有 __strong 的变量前必须初始化为 nil,所以这里使用使分配区域初始化为 0 的 calloc 函数来分配内存,也可以用 malloc 后用 memset 等将其填充为 0
//这样很危险
array = (id __strong *)malloc(entries, sizeof(id));
for(NSUInteger i = 0; i < entries; i++)
array[i] = nil;
这样内存区域没有初始化为 0,nil 会被赋值给 __strong 并被赋值了随机地址的变量中,从而释放一个不存在的变量,因此推荐使用 calloc
通过 calloc 函数分配的动态数组能完全像静态数组一样使用
array[0] = [[NSObject alloc] init];
但动态数组中操作赋有 __strong 的变量,需要自己释放所有元素
在只是简单地用 free 函数(即 free(array);)废弃数组用内存块的情况下,数组各元素所赋值的对象不能再次释放,从而引起内存泄露
因为在静态数组中,编译器能够根据变量作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,无从处理 。一定要将 nil 赋值给所有元素,使得其所赋值对象的强引用失效,从而释放对象,之后再 free
//释放
for(NSUInteger i = 0; i < entries; i++)
array[i] = nil;
free(array);
同初始化的注意事项相反,即使用 memset 将内存填充为 0 也不会释放所赋值的对象,会引起内存泄露
同样禁止使用 memcpy 函数拷贝数组元素以及 realloc 函数重新分配内存块