四个法则
- 自己生成的对象,自己持有。
- 非自己生成的对象,自己也能持有。
- 不在需要自己持有对象的时候,释放。
- 非自己持有的对象无需释放。
ARC 是苹果引入的一种自动内存管理机制,会根据引用计数自动监视对象的生存周期,实现方式是在编译时期自动在已有代码中插入合适的内存管理代码以及在 Runtime 做一些优化。
编译器在编译时会帮我们自动插入,包括 retain
、release
、copy
、autorelease
、autoreleasepool
变量标识符
在ARC中与内存管理有关的变量标识符,有下面几种:
__strong:
__strong 是默认使用的标识符。只有还有一个强指针指向某个对象,这个对象就会一直存活。
__weak:
__weak 声明这个引用不会保持被引用对象的存活,如果对象没有强引用了,弱引用会被置为 nil__unsafe_unretained:
__unsafe_unretained 声明这个引用不会保持被引用对象的存活,如果对象没有强引用了,它不会被置为 nil。如果它引用的对象被回收掉了,该指针就变成了野指针。__autoreleasing:
__autoreleasing 用于标示使用引用传值的参数(id *),在函数返回时会被自动释放掉。
变量标识符的用法如下:
Number* __strong num = [[Number alloc] init];
注意 __strong 的位置应该放到 * 和变量名中间,放到其他的位置严格意义上说是不正确的,只不过编译器不会报错。
属性标识符
类中的属性也可以加上标志符:
@property (assign/retain/strong/weak/unsafe_unretained/copy) Number* num
assign
表明 setter 仅仅是一个简单的赋值操作,通常用于基本的数值类型,例如CGFloat
和NSInteger
。
strong
表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行 retain
,旧值进行 release
,然后进行赋值操作。
weak
表明属性定义了一个非拥有者关系。当给属性设定一个新值的时候,这个值不会进行 retain
,旧值也不会进行 release
, 而是进行类似 assign
的操作。不过当属性指向的对象被销毁时,该属性会被置为nil。
unsafe_unretained
的语义和 assign
类似,不过是用于对象类型的,表示一个非拥有(unretained)的,同时也不会在对象被销毁时置为nil的(unsafe)关系。
copy
类似于 strong
,不过在赋值时进行 copy
操作而不是 retain
操作。通常在需要保留某个不可变对象(NSString最常见),并且防止它被意外改变时使用。
错误使用属性标识符的后果
如果我们给一个原始类型设置 strong\weak\copy
,编译器会直接报错:
Property with 'retain (or strong)' attribute must be of object type
设置为 unsafe_unretained
倒是可以通过编译,只是用起来跟 assign
也没有什么区别。
反过来,我们给一个 NSObject 属性设置为 assign,编译器会报警:
Assigning retained object to unsafe property; object will be released after assignment
正如警告所说的,对象在赋值之后被立即释放,对应的属性也就成了野指针,运行时跑到属性有关操作会直接崩溃掉。和设置成 unsafe_unretained
是一样的效果(设置成 weak
不会崩溃)。
unsafe_unretained
的用处
unsafe_unretained
差不多是实际使用最少的一个标识符了,在使用中它的用处主要有下面几点:
- 兼容性考虑。iOS4 以及之前还没有引入
weak
,这种情况想表达弱引用的语义只能使用unsafe_unretained
。这种情况现在已经很少见了。 - 性能考虑。使用
weak
对性能有一些影响,因此对性能要求高的地方可以考虑使用unsafe_unretained
替换weak
。一个例子是 YYModel 的实现,为了追求更高的性能,其中大量使用unsafe_unretained
作为变量标识符。
ARC规则
在ARC有效的情况下编译源代码,必须遵守一定的规则。
- 不能使用
retain/release/retainCount/autorelease
ARC有效时,实现retain/release/retainCount/autorelease
会引起编译错误。代码会标红,编译不通过。 - 不能使用NSAllocateObject/NSDeallocateObject
- 须遵守内存管理的方法命名规则
alloc,new,copy,mutableCopy,init
以init开始的方法的规则要比alloc,new,copy,mutableCopy更严格。该方法必须是实例方法,并且要返回对象。返回的对象应为id类型或方法声明类的对象类型,抑或是该类的超类型或子类型。该返回对象并不注册到autoreleasepool上。基本上只是对alloc方法返回值的对象进行初始化处理并返回该对象。
//符合命名规则
- (id) initWithObject;
//不符合命名规则
- (void) initThisObject;
不要显式调用dealloc
当对象的引用计数为0,所有者不持有该对象时,该对象会被废弃,同时调用对象的dealloc方法。ARC会自动对此进行处理,因此不必书写[super dealloc]。使用@autoreleasepool块替代NSAutoreleasePool
不能使用区域(NSZone)
对象型变量不能作为C语言结构体(struct、union)的成员
C语言结构体(struct、union)的成员中,如果存在Objective-C对象型变量,便会引起编译错误。
struct Data {
NSMutableArray *array;
};
显示警告:
ARC forbids Objective-C objects in struct
在C语言的规约上没有方法来管理结构体成员的生命周期。因为ARC把内存管理的工资分配给编译器,所以编译器必须能够知道并管理对象的生命周期。例如C语言的局部变量可使用该变量的作用域管理对象。但是对于C语言的结构体成员来说,这在标准上就是不可实现的。
要把对象类型添加到结构体成员中,可以强制转换为void *或是附加__unsafe_unretained修饰符。
struct Data {
NSMutableArray __unsafe_unretained *array;
};
__unsafe_unretained
修饰符的变量不属于编译器的内存管理对象。如果管理时不注意赋值对象的所有者,便可能遭遇内存泄露或者程序崩溃。
显示转换id和void *
在MRC时,将id变量强制转换void *变量是可以的。
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];
但是在ARC时就会编译报错,id型或对象型变量赋值给void *或者逆向赋值时都需要进行特定的转换。如果只想单纯的赋值,则可以使用“__bridge转换”
__bridge转换中还有另外两种转换,分部是“__bridge_retained”和“__bridge_transfer转换”
__bridge_retained转换与retain类似,__bridge_transfer转换与release类似。
void *p = (__bridge_retained void *)[[NSObject alloc] init];
NSLog(@"class = %@", [(__bridge id)p class]);
(void)(__bridge_transfer id)p;