iPhone开发之深入浅出 (1) — ARC是什么
iPhone开发之深入浅出 (1) — ARC是什么
新年伊始,万象更新。新一年开始,我们来更加深入了解一下iPhone开发的内部。作为开始,我们先来了解一下ARC。
ARC是什么
ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。
该机能在 iOS 5/ Mac OS X 10.7 开始导入,利用 Xcode4.2 可以使用该机能。简单地理解ARC,就是通过指定的语法,让编译器(LLVM 3.0)在编译代码时,自动生成实例的引用计数管理部分代码。有一点,ARC并不是GC,它只是一种代码静态分析(Static Analyzer)工具。
变化点
通过一小段代码,我们看看使用ARC前后的变化点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@interface NonARCObject : NSObject { NSString *name; } -(id)initWithName:(NSString *)name; @end @implementation NonARCObject -(id)initWithName:(NSString *)newName { self = [super init]; if (self) { name = [newName retain]; } return self; } -(void)dealloc { [name release]; [Super dealloc]; } @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@interface ARCObject : NSObject { NSString *name; } -(id)initWithName:(NSString *)name; @end @implementation ARCObject -(id)initWithName:(NSString *)newName { self = [super init]; if (self) { name = newName; } return self; } @end |
- 我们之前使用Objective-C中内存管理规则时,往往采用下面的准则
- 生成对象时,使用autorelease
- 对象代入时,先autorelease后再retain
- 对象在函数中返回时,使用return [[object retain] autorelease];
而使用ARC后,我们可以不需要这样做了,甚至连最基础的release都不需要了。
使用ARC的好处
使用ARC有什么好处呢?
- 看到上面的例子,大家就知道了,以后写Objective-C的代码变得简单多了,因为我们不需要担心烦人的内存管理,担心内存泄露了
- 代码的总量变少了,看上去清爽了不少,也节省了劳动力
- 代码高速化,由于使用编译器管理引用计数,减少了低效代码的可能性
不好的地方
- 记住一堆新的ARC规则 — 关键字及特性等需要一定的学习周期
- 一些旧的代码,第三方代码使用的时候比较麻烦;修改代码需要工数,要么修改编译开关
关于第二点,由于 XCode4.2 中缺省ARC就是 ON 的状态,所以编译旧代码的时候往往有"Automatic Reference Counting Issue"的错误信息。
这个时候,可以将项目编译设置中的“Objectice-C Auto Reference Counteting”设为NO。如下所示。
如果只想对某个.m文件不适应ARC,可以只针对该类文件加上 -fno-objc-arc 编译FLAGS,如下图。
ARC基本规则
- retain, release, autorelease, dealloc由编译器自动插入,不能在代码中调用
- dealloc虽然可以被重载,但是不能调用[super dealloc]
由于ARC并不是GC,并需要一些规则让编译器支持代码插入,所以必须清楚清楚了这些规则后,才能写出健壮的代码。
Objective-C对象
ObjectiveC中的对象,有强参照(Strong reference)和弱参照(Weak reference)之分,当需要保持其他对象的时候,需要retain以确保对象引用计数加1。对象的持有者(owner)只要存在,那么该对象的强参照就一直存在。
- 对象处理的基本规则是
- 只要对象的持有者存在(对象被强参照),那么就可以使用该对象
- 对象失去了持有者后,即被破弃
强参照 (Strong reference)
- (s1)
firstName作为”natsu”字符串对象的最初持有者,是该NSString类型对象的Strong reference。
- (s2)
这里将firstName代入到aName中,即aName也成为了@”natsu”字符串对象的持有者,对于该对象,aName也是Strong reference。
- (s3)
这里,改变firstName的内容。生成新的字符串对象”maki”。这时候firstName成为”maki”的持有者,而@”natsu”的持有者只有aName。每个字符串对象都有各自的持有者,所以它们都在内存中都存在。
- (s4)
追加新的变量otherName, 它将成为@”maki”对象的另一个持有者。即NSString类型对象的Strong reference。
- (s5)
将otherName代入到aName,这时,aName将成为@”maki”字符串对象的持有者。而对象@”natsu”已经没有持有者了,该对象将被破弃。
弱参照 (Weak reference)
接下来我们来看看弱参照 (Weak reference) 的使用方式。
- (w1)
与强参照方式同样,firstName作为字符串对象@”natsu”的持有者存在。即是该NSString类型对象的Strong reference。
- (w2)
使用关键字__weak,声明弱参照weakName变量,将firstName代入。这时weakName虽然参照@”natsu”,但仍是Weak reference。即weakName虽然能看到@”natsu”,但不是其持有者。
- (w3)
firstName指向了新的对象@”maki”,成为其持有者,而对象@”natsu”因为没有了持有者,即被破弃。同时weakName变量将被自动代入nil。
引用关键字
ARC中关于对象的引用参照,主要有下面几关键字。使用strong, weak, autoreleasing限定的变量会被隐式初始化为nil。
- __strong
变量声明缺省都带有__strong关键字,如果变量什么关键字都不写,那么缺省就是强参照。
- __weak
上面已经看到了,这是弱参照的关键字。该概念是新特性,从 iOS 5/ Mac OS X 10.7 开始导入。由于该类型不影响对象的生命周期,所以如果对象之前就没有持有者,那么会出现刚创建就被破弃的问题,比如下面的代码。
1 2 |
NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; NSLog(@"string: %@", string); //此时 string为空 |
如果编译设定OS版本 Deployment Target 设定为这比这低的版本,那么编译时将报错(The current deployment target does not support automated __weak references),这个时候,我们可以使用下面的__unsafe_unretained。
弱参照还有一个特征,即当参数对象失去所有者之后,变量会被自动付上nil (Zeroing)。
- __unsafe_unretained
该关键字与__weak一样,也是弱参照,与__weak的区别只是是否执行nil赋值(Zeroing)。但是这样,需要注意变量所指的对象已经被破弃了,地址还还存在,但内存中对象已经没有了。如果还是访问该对象,将引起「BAD_ACCESS」错误。
- __autoreleasing
该关键字使对像延迟释放。比如你想传一个未初始化的对像引用到一个方法当中,在此方法中实例化此对像,那么这种情况可以使用__autoreleasing。他被经常用于函数有值参数返回时的处理,比如下面的例子。
1 2 3 4 5 6 7 8 9 10 11 |
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError { .... *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary]; } .... { NSError *error = nil; [self generateErrorInVariable:&error]; NSLog(@"Error = %@", error); } |
又如函数的返回值是在函数中申请的,那么希望释放是在调用端时,往往有下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-(NSString *)stringTest { NSString *retStr = [NSString stringWithString:@"test"]; return [[retStr retain] autorelease]; } // 使用ARC -(NSString *)stringTest { __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"]; return retStr; } |
即当方法的参数是id*,且希望方法返回时对象被autoreleased,那么使用该关键字。
总结
- 今天,我们看到了基本的ARC使用规则
- 代码中不能使用retain, release, retain, autorelease
- 不重载dealloc(如果是释放对象内存以外的处理,是可以重载该函数的,但是不能调用[super dealloc])
- 不能使用NSAllocateObject, NSDeallocateObject
- 不能在C结构体中使用对象指针
- id与void *间的如果cast时需要用特定的方法(__bridge关键字)
- 不能使用NSAutoReleasePool、而需要@autoreleasepool块
- 不能使用“new”开始的属性名称 (如果使用会有下面的编译错误”Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects”)
今后,我们将更加深入ARC,学习其更多的特性。
相关文章
- iPhone开发之深入浅出 (7) --- ARC总结 - (2012-03-28)
- iPhone开发之深入浅出 (6) --- ARC之对象转型 - (2012-03-24)
- iPhone开发之深入浅出 (5) --- ARC之Outlet与弱引用 - (2012-03-08)
- iPhone开发之深入浅出 (4) --- ARC之循环参照 - (2012-03-01)
- iPhone开发之深入浅出 (3) --- ARC之前世今生 - (2012-02-19)
- iPhone开发之深入浅出 (2) --- ARC之@property使用 - (2012-01-04)
2012年01月02日 01:25
不太明白“ 对象代入时,先autorelease后再retain”
您能举个例子么?
2012年01月05日 14:09
好的,过几天总结一下ARC之前Objective-C中内存管理的规则;解释一下你的提问。
2012年01月06日 17:57
NSString *retStr = [NSString stringWithString:@"test"];
return [[retStr retain] autorelease];
这段代码真是蛋疼 [NSString stringWithString] 本来就是返回一个autorelease的对象,直接return就好了,或者改成:
NSString *retStr = [[[NSString alloc] initWithString:@”test”] autorelease];
return retStr;
这几个例子跟csdn上面有篇博客里面的一模一样,连错都一样。
2012年01月06日 23:31
我觉得 应该不能算错吧?
何况多retain一次之后 能增加这个 obj的 生命周期吧?
2012年02月02日 11:52
看过几篇类似的文章,发现这篇讲的比较详细。学习中!
2012年03月26日 17:03
多谢分享,在arc内存管理中,遇到几个问题,希望能得到指导,谢谢!
问题一:
h文件定义属性@property (nonatomic,strong) NSArray * strongarray1;
m文件中访问时需要写成self.strongarray1吗?根据测试,发现写不写self对于内存管理是一样的。
不开启arc时,
NSArray * aa=[[NSArray alloc]initWithObjects:@”1″,@”2″,@”3″, nil];
self.strongarray1=aa;此时retain count是2。
开启arc后,
NSArray * aa=[[NSArray alloc]initWithObjects:@”1″,@”2″,@”3″, nil];
self.strongarray1=aa;是2个强引用。
写成strongarray1=aa;也是2个强引用,这是为什么呢?
问题二:
未开启arc时,
NSArray * aa=[[NSArray alloc]initWithObjects:@”1″,@”2″,@”3″, nil];
NSArray * bb=aa;此时只是指针赋值,retaincount并不会加1。
开启arc后,
NSArray * aa=[[NSArray alloc]initWithObjects:@”1″,@”2″,@”3″, nil];
NSArray * bb=aa;
由于aa 和bb 都是强引用,我的理解是retaincount是2,当然在arc下是不能打印retaincount的。
为什么同样的一句NSArray * bb=aa;在未开启arc时只是指针赋值,不增加retaincount。开启arc后,强引用也会加一?
2012年03月26日 23:40
问题1:
写成self.strongarray1和写成strongarray1不都是一样,不都是该类的成员变量嘛。
问题2:
我在《iPhone开发之深入浅出 (3) — ARC之前世今生》中间已经解释了:ARC有效时,id *obj == id __autoreleasing *obj;
也就是说这里,bb其实是 — NSArray __autoreleasing *obj; 而赋值动作只是再一次将aa放入了autoreleasepool中(第一次是aa初始化时)
另外,想在ARC有效时打印retaincount可以使用 Core Foundation 的API — CFGetRetainCount;不过要经过__bridge_xxxx 的类型转换,该部分今后的章节中会涉及到,请继续关注我的博客。
2012年03月27日 12:47
感谢回复!
也就是说这里,bb其实是 — NSArray __autoreleasing *obj; 而赋值动作只是再一次将aa放入了autoreleasepool中(第一次是aa初始化时)//第一次aa初始化时采用的是[NSArray alloc],应该不是把aa放入了autoreleasepool吧?是不是自动调用了[aa release];?
第二次NSArray * bb=aa;才是把aa放入了autoreleasepool吧?
非常感谢!
2012年03月27日 22:58
ARC有效时:
NSArray * aa=[[NSArray alloc]initWithObjects:@”1″,@”2″,@”3″, nil];
等于ARC无效时的下面代码:
NSArray * aa=[[[NSArray alloc]initWithObjects:@”1″,@”2″,@”3″, nil] autorelease];
也就是说aa一被创建就被自动放入了autopool中。
2012年04月22日 15:19
NSString __weak *string = [[NSString alloc] initWithFormat:@”First Name: %@”, [self firstName]];
NSLog(@”string: %@”, string); //此时 string为空
NSString __weak *string = [[NSString alloc] initWithString:@”xxxx”];
NSLog(@”string: %@”, string); //此时 string为xxx
请问大神,为什么使用initWithString的话,对象不会被释放掉呢?
还有给个小建议,评论的颜色太浅了,看起来太吃力了,虽然说要突出博主内容,但是尊重尊重评论者也是比较重要的~
2014年10月28日 15:14
同困惑这个问题,请问你搞清楚了吗
2012年07月16日 18:00
我想问下 你说的那个被 “破弃” 是指什么? 就是释放掉吗? 内存里的内容也会被释放吗?
2013年11月20日 15:23
-(NSString *)stringTest
{
__autoreleasing NSString *retStr = [NSString alloc] initWithString:@”test”];
return retStr;
}
这里为什么要加__autoreleasing?不加这个修饰符有什么不妥吗?
2014年03月02日 20:25
Pro Multithreading and Memory Management for iOS and OS X with ARC, Grand Central Dispatch, and Blocks
应该是这本书上的内容吧