前言:
- 使用xcode4.2或以上版本;
- 使用LLVM编译器3.0或以上版本;
- 编译器选项中设置ARC为有效;
- 不能使用 retain/release/retainCount/autorelease
- 不能使用 NSAllocateObject/NSDeallocateObject
- 须遵守内存管理的方法命名规则
- 不要显示调用dealloc
- 使用 @autorelease 块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结固体的成员
- 显示转换“id”和"void *"
所有权修饰符:
- __strong修饰符
- __weak修饰符
- __unsafe_unretained修饰符
- __autoreleasing修饰符
__strong修饰符:
//以下两种写法在ARC有效时是等价的
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
那么在ARC有效时,__strong修饰符是怎么样的运行机制呢?
{
Person __strong *p1 = [[Person alloc] init];
//p1是个强指针,一般情况下都会省略__strong修饰符
//使用p1
//使用结束后不再需要且不允许调用[p1 release]方法
}
/*
在这里p1的生命周期结束,p1被废弃
Person对象已经没有强指针指向它了
编译器此时废弃内存中的Person对象
*/
如果有多个指针指向这个对象,其实情况也一样。只要记住,一个对象只要在没有强指针指向时,才会被废弃。
假如有个这样的Person类和Dog类。
Person.h
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Person : NSObject{
//宠物,默认是__strong
Dog *_dog;
}
-(void)setDog:(Dog *)dog;
@end
Person.m
#import "Person.h"
@implementation Person
-(void)setDog:(Dog *)dog
{
_dog = dog;
}
@end
Dog.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Dog : NSObject{
//主人 默认是__strong
Person *_owner;
}
-(void)setOwner:(Person *)owner;
@end
Dog.m
#import "Dog.h"
@implementation Dog
-(void)setOwner:(Person *)owner
{
_owner = owner;
}
@end
如果在main函数里这样使用:
int main(int argc, char *argv[])
{
Person *p1 = [[Person alloc] init];
Dog *d1 = [[Dog alloc] init];
[p1 setDog:d1];
[d1 setOwner:p1];
return 0;
}
/*
p1和d1指针失效
但是Person对象和Dog对象,因为形成了循环引用
互相有强指针指向对方
因为Person对象和Dog对象都不会被废弃
造成内存泄漏
*/
用一张图说明
为了解决这个问题,苹果提供了__weak修饰符。
__weak修饰符:
Person __weak *p1 = [[Person alloc] init];
如果这样写,编译器是无法通过的。会报Assigning retained object to weak property object will be released after assignment的警告。
id __weak obj3 = [NSMutableArray array];
为什么呢?这里先不介绍,留在文章的第三部分,关于autorelease和返回值那一块介绍。
{
Person *p1 = [[Person alloc] init];
Person __weak *p2 = p1;
//如果把p1指向空,Person对象又没有了指向它的强指针。会被废弃。
//此时通过p2已经找不回那个Person对象了。而且p2会被编译器置为nil
p1 = nil;
NSLog(@"p2: %@", p2); //输出为null
}
由此可以猜测,弱指针不会使得引用计数+1,虽然通过弱指针同样可以使用到对象,但是弱指针不作为对象是否废弃的标准。
那么有了__weak之后,又怎么可以避免循环引用呢?还是使用上面的例子,只需要修改一行代码。
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Dog : NSObject{
//主人 默认是__strong,手动设为__weak
Person __weak *_owner;
}
-(void)setOwner:(Person *)owner;
@end
一张图解释:
如此便解决了循环引用的问题。
__unsafe_unretained修饰符:
我之所以把它理解为不安全的弱指针,是因为它本质也是一个弱指针,除此之外和__weak不同的地方在于,__weak修饰的指针在所指向的对象被废弃的时候,会被编译器置空,避免野指针的产生。而__unsafe_unretained修饰的指针是不会被自动置空的,这样就存在野指针的风险。因此不建议使用__unsafe_unretained修饰符。我目前也未能很好地理解它有什么存在的价值。不过幸运的是,基本上现在也可以不太管它了,它主要用于ios5及以前的时候。__autoreleasing修饰符:
@autoreleasepool {
/*
取得非自己生成并持有的对象
*/
id __strong obj = [NSMutableArray array];
/*
因为变量obj为强引用
所以自己持有对象
并且该对象有编译器判断其方法名后
自动注册到autoreleasepool
*/
}
/*
因为变量obj超出其作用域,强引用失效
所以自动释放自己持有的对象
同时,随着@autoreleasepool块结束,
注册到autoreleasepool中的所有对象
被自动释放
如果所有者不怎在,所以废弃对象
*/
id obj = [[NSObject alloc] init];
@autoreleasepool {
id __autoreleasing obj2 = obj;
NSLog(@"%@",obj2);
}
NSLog(@"%@",obj);
两个NSLog输出是一样的。可以看出变量obj指向的对象没被废弃,因为obj2废弃后这个对象还有obj这个强指针指向。
//别忘了,默认是__strong
id obj = [[NSObject alloc] init];
for (int i = 0; i < 10000; i++)
{
id __autoreleasing obj2 = obj;
}
像这样没有明显地使用@autoreleasepool,就是把10000个obj2注册到main runloop下的autorelease pool里,然后执行完这个for循环后再一次性废弃这10000个__autoreleasing修饰的变量。一般来说,当需要注册到autorelease pool的变量太多的话,最好自己手动创建@autoreleasepool,不然会使得main runloop里的autorelease pool太撑,降低它的性能。
id obj = [[NSObject alloc]init];
for (int i = 0; i < 100000000; i++)
{
@autoreleasepool
{
id __autoreleasing obj2 = obj;
}
}
第一个小结
基本上四个修饰符表现出来的基本特性就是这样子了。但是它们真正的内涵可远远不止于此,想要真正理解它们,还有很多内容需要知道。毕竟看起来越简单的东西,背后一定有着更复杂的实现。我们享受这ARC方便好用的同时,它背后一定有着更复杂的实现机理。现在可以整理一下思路,准备继续进发吧。
再议autorelease和函数返回值
还记得第一篇讲到了在非ARC(MRC)下,autorelease和函数返回值。这篇,就讲讲它们在ARC下的表现。self.property = [[NSObject alloc] init];//property的定义是@property (nonatomic, retain) NSObject *property;
self.name = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
以及在自己写的函数中:
+ (MyCustomClass *) myCustomClass
{
return [[MyCustomClass alloc] init]; // 不用 autorelease
}
这些写法都是 OK 的,也不会出现内存问题。
对于 retained return value:
When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, before leaving all local scopes.When receiving a return result from such a function or method, ARC releases the value at the end of the full-expression it is contained within, subject to the usual optimizations for local values.
可以看到基本上 ARC 就是帮我们在代码块结束的时候进行了 release:
NSObject * a = [[NSObject alloc] init];
self.property = a;
//[a release]; 我们不需要写这一句,因为 ARC 会帮我们把这一句加上
对于 unretained return value:
When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool.ARC performs no extra mandatory work on the caller side, although it may elect to do something to shorten the lifetime of the returned value.
这个和我们之前在 MRC 中做的不是完全一样。ARC 会把对象的生命周期延长,确保调用者能拿到并且使用这个返回值,但是并不一定会使用 autorelease把对象注册到autoreleasepool中,文档写的是在 worest case 的情况下才可能会使用,因此调用者不能假设返回值真的就在 autorelease pool 中。从性能的角度,这种做法也是可以理解的。如果我们能够知道一个对象的生命周期最长应该有多长,也就没有必要使用 autorelease 了,直接使用 release 就可以。如果很多对象都使用 autorelease 的话,也会导致整个 pool 在 drain 的时候性能下降。那么问题就来了,什么时候会把返回值的对象注册到autoreleasepool呢?这取决于我们接这个返回值的变量用什么修饰符去修饰。不同修饰符修饰时,ARC会做相对应的操作。
_strong和unretained return value
{
id __strong obj = [NSMutableArray array];
}
这里补充《OC高级编程--ios与os x多线程和内存管理》这本书上贴出的关于array的源码:
+ (id) array
{
return [[NSMutableArray alloc] init]; } //返回一个retained return value,但编译器扫描到当前方法是array方法,会作出优化
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj); // 相当于调用retain
objc_release(obj);
+ (id) array
{
id obj = objc_msgSend(NSMutableArray, @selector(alloc)); //用一个__strong来接收
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj); //把obj注册到autorelease pool,相当于MRC下调用autorelease,到时候会自动为这个对象的引用计数-1,ARC下就是把obj这个强指针废弃掉,对象没有强指针指向,紧接着废弃这个对象
}
虽然说ARC模式下,我们不可以自己手动执行release和retain之类的各种方法,但其实是因为编译器会帮我们插入这方面的代码来管理对象。因此可以看到编译器改写后的代码具有这些方法的影子。
此时,ARC 可以使用一些优化技术。在调用 objc_autoreleaseReturnValue() 时,会在栈上查询 return address 以确定 return value 是否会被直接传给 objc_retainAutoreleasedReturnValue() 。 如果没传,说明返回值不能直接从提供方发送给接收方,这时就会调用 autorelease 。反之,如果返回值能顺利的从提供方传送给接收方,那么就会直接跳过 autorelease 过程,并且修改 return address 以跳过 objc_retainAutoreleasedReturnValue() 过程,这样就跳过了整个 autorelease 和 retain 的过程。通过 objc_autoreleaseReturnValue函数和objc_retainAutoreleasedReturnValue函数的写作,可以不将对象注册到autorelease pool中而直接传递,这一过程达到最优化。
因此, 当我们使用__strong修饰的指针来接收unretained return value时,编译器就不把它注册到autorelease pool了,直接将它返回。返回后的结果,就是一个强指针,指向一个对象。
_weak和unretained return value
id __weak obj2 = [[NSMutableArray alloc] init];//会报对象被废弃的警告,随后打印obj2得到的是null
id __weak obj3 = [NSMutableArray array];//不会报错
现在可以就这个现象解释一下了。已知第一个方法返回一个retained return value,返回的时候如果没有强指针指向它,会一出生就废弃。因为编译器会自动插入一条release操作式这个对象的引用计数-1,假如没有强指针指向它为它的引用计数+1,这时候引用计数就会减为0,对象被废弃。
{
id __weak obj1 = obj;
NSLog(@"%@",obj1);
}
该代码会被编译器转化成如下形式:
/*编译器的模拟代码*/
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destoryWeak(&obj1);
与赋值时相比,在使用附有_weak修饰符的变量的情形下,增加了对objc_loadWeakRetained函数和objc_autorelease函数的调用。这些函数的动作如下。
如此会有三个变量注册到autoreleasepool中,性能非常不友好。下面的使用可以解决这样的问题:{ id __weak obj1 = obj; NSLog(@"%@",tmp); NSLog(@"%@",tmp); NSLog(@"%@",tmp); }
{
id __weak obj1 = obj;
id tmp = obj1;
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
}
_autoreleasing和unretained return value
对于retained return value考虑以下代码:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
它的模拟代码是:
/将上面的源码转换成编译器的模拟源代码如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init)); objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
可以看到对象被注册到autoreleasepool,这没什么好奇怪的,因为我们使用了__autoreleasing修饰的obj来接收。
对于unretained return value考虑以下代码:
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
它的模拟代码是:
//将上面的源码转换成编译器的模拟源代码如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj); objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
为了方便,再贴上array的模拟代码:
+ (id) array
{
id obj = objc_msgSend(NSMutableArray, @selector(alloc)); //用一个__strong来接收
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj); //把obj注册到autorelease pool,相当于MRC下调用autorelease,到时候会自动为这个对象的引用计数-1,ARC下就是把obj这个强指针废弃掉,对象没有强指针指向,紧接着废弃这个对象
}
由此可以看到,编译器在这里再一次做了优化。一般情况下array方法的返回值会通过调用objc_autoreleaseReturnValue把对象注册到autorelease pool,此时,编译器插了一条objc_retainAutoreleasedReturnValue();通过上面的学习知道这会跳过把对象注册到autoreleasepool的操作。紧接着是__autoreleasing修饰符产生的objc_autorelease(obj)方法,把对象注册到autorelease pool。这样,就可以避免因为__autoreleasing修饰符和array方法的搭配使用,而把一个对象注册到autorelease pool两次。
第二个小结:
看到这里是不是觉得有点混乱,一会引用计数,一会强指针。补充一下说明吧:
在MRC中,是通过引用计数来管理对象的生命周期,并没有强弱指针的概念,还是第一篇文章的例子:
int main(int argc, const charchar * argv[]) {
id *obj1 = [[NSObject alloc] init];
NSObject *obj2 = obj1;
//obj2也指向了这个NSObject对象,但NSObject引用计数不会自动+1,程序员有义务为其引用计数+1
[obj2 retain];
//既然有了[obj2 retain],那么obj2用完之后也要有[obj2 release],retain和release方法需要成对出现。
[obj2 release];
//此时,仍然可以通过obj2指针使用到NSOjbect对象,但这是不合规范且危险的
NSLog(@"NSObject obj2:%@", obj2);
[obj1 release];
//到这里,NSObject对象的引用计数减为0了
//此时如果再访问NSObject对象就会报错
//NSLog(@"NSObject retaincount: %ld", (unsigned long)[obj2 retainCount]);
//所以这时候obj1和obj2都成为野指针了,这个问题会在ARC中得到解决。也可以手动设置obj1=nil,obj2=nil解决
return 0;
}
创建一个变量指针指向一个已有对象的时候,如obj2指向NSObject对象,编译器也不会自动为对象的引用计数+1,因此,本着负责任的态度,都需要为它的引用计数+1(retain),用完再-1(release)。此外,release并不代表obj2不再指向这个对象,仅仅是让引用计数-1。那如果不是指向一个已经的对象,而是创建对象的时候,就要分以下两种情况了。
当使用alloc/new/copy/mutableCopy方法时:NSObject *obj1 = [[NSObject alloc] init]。这样创建的对象在创建的时候引用计数就置1了,因此不需要额外地使用[obj1 retain],用完调用[obj1 release]即可。
当使用类方法时:如id obj = [NSMutableArray array]。已知类方法内部的实质也是调用alloc/new/copy/mutableCopy来创建对象,因此一创建它们的引用计数也已经为1了,但类方法的区别在于,它会把对象注册到autorelease pool,用完这个对象不用为其release。这个pool一般是runloop下的pool,它会在某个时候自己调用drain,把注册到里面的对象引用计数-1。如果是注册到自己创建的pool,那就要记得自己调用drain方法。我们自己编写类方法的时候,也应该遵守苹果的这个规范,把对象注册到autorelease pool。
在ARC中,有了强弱指针,就不用再考虑引用计数的问题了,引用计数编译器会自动帮我们处理。不然这个机制就不会叫自动引用计数了。我们只需要简单地考虑有没有强指针指向这个对象。多一个强指针指向这个对象时,编译器自动帮这个对象的引用计数+1,少一个强指针指向这个对象时,编译器自动帮这个对象的引用计数-1。如果没有强指针指向这个对象也就意味着这个对象引用计数为0,需要废弃了。而对于autorelease pool的问题,它仍旧是需要的,因为它在处理返回值问题以及批量处理对象问题上都能发挥很好的作用。所以也就有了__autoreleasing修饰符了,这个修饰符能把对象注册到autorelease pool里。但就算没有写这个修饰符,在类方法的返回值上,编译器也会机智地根据实际需要自动帮我们把对象注册到autorelease pool里(看接收的对象,不一定注册的)。而对于autorelease pool里的对象,也可以理解成有一个会自动释放的强指针指向它,这个强指针会随着它所在的pool结束而废弃。