一篇文章彻底弄清ARC始末
转:点击打开链接
自动引用计数(ARC)是编译器的一个特色,提供了Objective-C对象的自动内存管理机制。比起不得不考虑retain和release操作,ARC让你更加专注于应用中那些有趣的代码,如对象图,对象关系。
摘要(Summary)
通过在编译期添加代码的方式,ARC保证所有对象按需存在,按需释放。从概念上来讲,它与手动引用计数(参见 Advanced Memory Management Programming Guide)有着相同的内存管理约定,二者都会为你添加合适的内存管理方法调用。 为了编译器能生成正确的代码,ARC限定了你可以使用的一些方法,以及toll-free桥接的使用方式(参见 “Toll-Free Bridged Types”).与此同时ARC还为对象引用(object references )和声明式属性(declared properties)引进了新的生命周期限定符(lifetime qualifiers )。 ARC适用于OS X v10.6和v10.7(64位)下的Xcode4.2,以及IOS 4和IOS5.OS X v10.6和IOS4不支持弱引用。Xcode提供了自动化的工具,完成ARC转换过程中需要手工操作的部分(例如删除retain和release方法调用),并且帮助你在 迁移过程中不能自动完成的操作(选择Edit->Refactor->Convert to Objective-C ARC)。迁移工具将工程中的所有文件使用ARC进行转换。如果你觉得在某些文件中使用手动引用计数会更方便,那么你也可以选择在单独文件中使用ARC。
参见:
Advanced Memory Management Programming
Guide
Memory Management Programming Guide
for Core Foundation
ARC概述(ARC Overview)
ARC会评估对象所需的生命期,并会在编译期为你自动插入合适的内存管理调用方法,取代之前你不得不考虑何时需要使用 retain, release以及 autorelease的操作方式。编译器也会为你生成合适的 dealloc方法。总的来说,如果你仅使用ARC,传统的Cocoa命名规范只会在你需要与使用手动引用计数的代码交互时才是重要的。一个完整正确的Person类的实现看起来可能是这样的:
@interface Person : NSObject @property NSString *firstName; @property NSString *lastName; @property NSNumber *yearOfBirth; @property Person *spouse; @end @implementation Person @end
(对象属性(properties)默认是强类型的( strong); strong属性描述于 “ARC Introduces New Lifetime Qualifiers.”)
使用ARC,你可以如下实现一个 contrived方法:
- (void)contrived { Person *aPerson = [[Person alloc] init]; [aPerson setFirstName:@"William"]; [aPerson setLastName:@"Dudney"]; [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]]; NSLog(@"aPerson: %@", aPerson); }
ARC会维护内存管理,所以 Person和 NSNumber对象都不会泄露。
你也可以安全的实现 Person类中的 takeLastnameFrom:方法。
- (void)takeLastNameFrom:(Person *)person { NSString *oldLastname = [self lastName]; [self setLastName:[person lastName]]; NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]); }
ARC保证 oldLastName在 NSLog语句之前不会被释放。
【ARC强制执行新规则】(ARC Enforces New Rules)
为了正常运转,ARC使用了一些在使用其它编译模式下没有的新规则。这些规则意在提供完全可靠的内存管理模型;在某些情况下,它们仅使用最佳实践方法,在其它情况下,它们仅简化你的代码或明显的做出推论告知你不需要处理内存管理。如果
你违反了这些规则,你会马上得到一个编译期错误,而不是在运行期可能会显现的一个狡猾的bug。
1.不能显示的调用dealloc,实现或调用 retain, release, retainCount,或 autorelease。
同样也不要能使用 @selector(retain), @selector(release), 等等类似的选择器。
如果你需要管理资源而不是释放实例变量,那你可以实现 dealloc方法。你不需要(事实上你不能)释放实例变量,但你可能需要在系统类和其它的未使用ARC代码中调用 [systemClassInstance setDelegate:nil] 方法。
在ARC中自定义的 dealloc方法不要调用 [super dealloc]方法(它实际上会导致编译器错误)。到super的链式调用是自动的并且是编译器强制执行的。
你仍可以在Core Foundation样式的的对象上,使用 CFRetain, CFRelease,和其它相关的函数。
2.你不能使用 NSAllocateObject 或 NSDeallocateObject
你使用 alloc来创建对象;运行时系统会注意释放这些对象。
3.你不能在C语言结构体中使用对象指针。 与其使用一个结构体(
struct),不如创建一个Objective-C类来管理数据。
4. id与 void*之间不能随意转换
你必须使用特定的类型转换来告诉编译器对象的生命周期。你需要在Objective-C对象和以函数参数传入的Core Foundation类型值之间进行这样的转换。有关详情,参见 “Managing
Toll-Free Bridging”。
5.你不能使用 NSAutoreleasePool对象
ARC 提供了 @autoreleasepool来代替。这比 NSAutoreleasePool更高效。
6.你不能使用内存区(memory zones)。
再也没有使用 NSZone的必要了——现代的Obj-C运行时会永远忽略它。
为了允许与自动retain-release的代码进行交互,ARC在方法命名上加上了一个约束:
你不能以 new为开头命名一个访问器的名字。这反过来意味着你不必声明一个以new开头的属性,除非你指定一个不同名称的getter方法:
// Won't work: @property NSString *newTitle; // Works: @property (getter=theNewTitle) NSString *newTitle;
【ARC引入新的生命周期限定符】(ARC Introduces New Lifetime Qualifiers)
ARC为对象引入了几种新的生命周期限定符,以及弱类型引用( weak
references)。弱类型应用不会延长它所指向对象的生命周期,一旦对象没有强引用指向对象时,弱引用会自动变为 nil。
在程序中,你应该充分利用这些限定符来管理对象图。尤其是,ARC不能阻止强引用循环(strong reference cycles,之前称为retain cycles,参见“Practical Memory Management”)的发生。审慎的使用弱类型关系会帮助你避免创建强引用循环。
【Property的属性】(Property Attributes)
weak和 strong关键字是新引入的property声明属性,如下例所示:
// 以下声明等价于 @property(retain) MyClass *myObject; @property(strong) MyClass *myObject;
// 以下声明等价于"@property(assign) MyClass *myObject;" // 不同之处在于如果MyClass的实例变量释放时,其属性值会设置为nil,而不会保存为一个野指针 @property(weak) MyClass *myObject;
ARC下,对象类型的声明默认为 strong
【变量限定符】(Variable Qualifiers)
你可以向使用其它变量的限定符那样,比如说, const,来使用以下生命周期限定符,
<span style="font-family: 宋体;">__strong </span>
<span style="font-family: 宋体;">__weak </span>
__unsafe_unretained
__autoreleasing
__strong是默认的。只要有强类型指针指向一个对象,那么该对象会一直”生存“下去。
__weak表明一个不会维持所持对象生命期的引用。当没有强引用指向该对象时,弱引用会设置为nil。
__unsafe_unretained指定一个引用,该引用不会维持所持对象的生命期,并且在没有强引用指向对象时也不会设置为nil。如果它所指向的对象已经被释放,那么它会成为一个野指针。
__autoreleasing 用以指示以引用(id*)传入的参数并在retun后自动释放。
你应该正确的修饰变量。在对象变量的声明时使用限定符的正确格式为:
ClassName * qualifier variableName;
例如:
MyClass * __weak myWeakReference; MyClass * __unsafe_unretained myUnsafeReference;
从技术上讲其它的变体写法都是错误的,但编译器都会”宽恕“它们。要弄清该问题,参见http://cdecl.org/.
在堆栈上使用 __weak变量时要当心。考虑以下的例子:
NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; NSLog(@"string: %@", string);
虽然 string是在初始化赋值之后使用,但是在赋值的时候并没有其它强引用指向字符串对象;因此字符串对象会马上释放掉。log语句显示 stirng的值为 null。(编译器对这种情况会提示警告)
你也要注意通过引用方式传入的对象。以下代码会正常运转
NSError *error; BOOL OK = [myObject performOperationWithError:&error]; if (!OK) {
而error的声明是隐式的:
NSError * __strong e;
方法的声明通常是:
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
因此编译器会重写代码:
NSError * __strong error; NSError * __autoreleasing tmp = error; BOOL OK = [myObject performOperationWithError:&tmp]; error = tmp; if (!OK) {
本地变量声明( __strong)和参数( __autoreleasing)之间的区别导致编译器创建临时变量。在获取__strong变量的地址时你可以通过将参数声明为 id __storng*来获得其原始指针。或者你可以将变量声明为 __autoreleasing。
【使用生命周期限定符来避免强类型循环引用】(Use Lifetime Qualifiers to Avoid Strong Reference Cycles)
你可以使用生命周期限定符来避免强类型循环引用。例如,通常如果你的对象图形成于父-子层级结构中,父对象需要引用它的子对象,反之亦然, 那么你构造parent-to-child的关系为强类型,child-to-parent的关系为弱类型。其它情况可能会更加微妙,尤其是在涉及到
block对象时。
在手动引用技术的模式下, __block id x,不会保留x。在ARC模式下, __block id x;默认会保留x(就像其它值一样)。为了在ARC下达到手动引用技术的效果,你可以使用 __unsafe__unretained __block
id x ;然而,就像 __unsafe_unretained名字蕴含的那样,拥有一个未保留变量(non-retained variable)是危险的(因为他可能是一个野指针),因此最好不要使用。有两个更好的选择是要么使用 __weak(如果你不需要支持IOS4或OS X v10.6),要门设置 __block值nil来打破循环引用。
以下的代码片段说明了在手动引用技术下时常会使用的模式。
MyViewController *myController = [[MyViewController alloc] init…]; // ... myController.completionHandler = ^(NSInteger result) { [myController dismissViewControllerAnimated:YES completion:nil]; }; [self presentViewController:myController animated:YES completion:^{ [myController release]; }];
如上所属,你可以使用 __block限定符来替代,并在 completion处理方法中将myController的值设置为nil:
MyViewController * __block myController = [[MyViewController alloc] init…]; // ... myController.completionHandler = ^(NSInteger result) { [myController dismissViewControllerAnimated:YES completion:nil]; myController = nil; };
或者,你可以使用一个临时的 __weak变量。以下代码列举了一个简单的实现:
MyViewController *myController = [[MyViewController alloc] init…]; // ... MyViewController * __weak weakMyViewController = myController; myController.completionHandler = ^(NSInteger result) { [weakMyViewController dismissViewControllerAnimated:YES completion:nil]; };
而对于特殊的循环,你应该使用:
MyViewController *myController = [[MyViewController alloc] init…]; // ... MyViewController * __weak weakMyController = myController; myController.completionHandler = ^(NSInteger result) { MyViewController *strongMyController = weakMyController; if (strongMyController) { // ... [strongMyController dismissViewControllerAnimated:YES completion:nil]; // ... } else { // Probably nothing... } };
某些情况下如果一个类不兼容__ weak,你可以使用 __unsafe_unretained。但这也不适用于特殊的循环,因为在实际问题中验证 __unsafe_unretained指针是否仍有效或是否仍指向相同的对象,是非常困难或根本不可能实现的。
【ARC使用新声明来管理自动释放池】(ARC Uses a New Statement to Manage Autorelease Pools)
使用ARC,你不能直接使用NSAutoreleasePool类来管理自动释放池。相反,你需要使用 @autoreleasepool块:
@autoreleasepool { // Code, such as a loop that creates a large number of temporary objects. }
这个简单的结构让编译器来判断引用计数的状态。在入口,自动释放池会被push。在正常退出( break,return,goto,fall-through,dengd)自动释放池会被pop。出于对已有代码兼容性的考虑,如果因异常导致退出,自动释放池将不会被pop。
该语法在所有Objecti-C模式下都可用。相比使用 NSAutoreleasePool类来说, @autoreleasepool更加高效;因此鼓励你在使用 NSAutoreleasePool的地方使用 @autoreleasepool。
【跨平台下Outlets管理保持一致的模式】(Patterns for Managing Outlets Become Consistent Across Platforms)
ARC下的IOS和OS X中声明outlets的模式已经发生变化,并且在跨平台下保持一致性。你通常应该接受的是:outlets应该是 weak的,除了那些在nib文件(或storyboard屏)中File‘s Owner指向的顶级对象应该是
strong的。
Resource
Programming Guide.中的 ”Nib
Files“做出了全面的解释。
【堆变量会初始化为nil】(Stack Variables Are Initialized with nil)
使用ARC,strong,weak,以及autoreleasing的堆变量现在会隐式的初始化为nil,例如:
- (void)myMethod { NSString *name; NSLog(@"name: %@", name); }
输出的 name的值为null而不可能是程序崩溃。
【使用编译器指令来启用或禁用ARC】(Use Compiler Flags to Enable and Disable ARC)
使用 -fobjc-arc编译指令来启动ARC。如果在某些文件中使用手动引用技术对你来说更方便些,你可以选择在单独的文件上使用ARC。对于默认使用ARC的工程,你可以使用 fno-objc-arc编译指令来禁用某个文件的ARC。
Xcode4.2及更高版本,OS X v10.6及更高版本(64为应用),IOS4版本或更高版本支持ARC。OS X v10.6和IOS4不支持弱引用。Xcode4.1及早期版本的Xcode不支持ARC。
【管理Toll-Free桥接】(Managing Toll-Free Bridging)
在许多Cocoa应用中,你可以使用Core Foundation样式的对象,不管是来自于Core Foundation框架本身(例如, CFArrayRef 或 CFMutableDictionaryRef),还是来自于诸如Core
Graphics一样采用Core Foundation约定的框架(你可能使用向 CGColorSpaceRef andCGGradientRef样式的类型)。
编译器不会自动管理Core Foundation对象的生命周期;你必须调用符合Core Founda内存管理规则的CFRetain和CFRelease方法(或者合适的特殊类型变体)来释放对象。(参见 Memory
Management Programming Guide for Core Foundation)
如果你在Objective-C对象和Core Foundation样式对象间执行类型转换,你要告诉编译器对象的归属语义,不管对象是使用类型转换(定义在objc/runtime.h)或Core Foundation样式的宏(定义在NSObject.h)。
__bridge在Objective-C和Core Foundation间的指针转换不附加对象的所有权。
__bridge_retained或 CFBridgingRetain将Objectiv-C指针转换为Core Foundation指针,并且将对象的所有权转给你。你负责调用CFRelease或相关的函数来放弃对象的所有权。
__bridge_transfer或 CFBridgingRelease将一个非Objective-C指针转移到Objective-C指针,并将对象所有权转交给ARC。ARC负责放弃对象的所有权。
例如,如果你有这样的代码:
- (void)logFirstNameOfPerson:(ABRecordRef)person { NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty); NSLog(@"Person's first name: %@", name); [name release]; }
你应该替代为:
- (void)logFirstNameOfPerson:(ABRecordRef)person { NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty)); NSLog(@"Person's first name: %@", name); }
编译器处理Cocoa方法返回的CF对象】(The Compiler Handles CF Objects Returned From Cocoa Methods)
编译器了解Objective-C的那些沿用Cocoa命名规则返回Core Foundation类型的方法(参见Advanced
Memory Management Programming Guide)。例如,编译器知道,在IOS中,UIColor的 CGColor方法返回的CGColor不会被拥有。你仍需使用合适的类型转换,如下例所示:
NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]]; [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
【使用所有权关键字对函数参数进行强制类型转换】(Cast Function Parameters Using Ownership Keywords)
函数调用中在Objective-C和Core Foundation对象之间进行转换,你要告诉编译器被传入的对象的所有权语义。Core Foundation的所有权规则在Core Foundation的内存管理规则中都已近说明了(参见 Memory
Management Programming Guide for Core Foundation);Objective-C对象的规则在 Advanced
Memory Management Programming Guide.有说明。
在下面的代码片段中,传入 CGGradientCreateWithColors函数的数组需要一个合适的类型转换。 arrayWithObjects:方法返回的对象的所有权不会传给函数,因此需要用 __bridge转换。
NSArray *colors = <#An array of colors#>; CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
以下方法实现中展示了代码片段。要注意的是遵从Core Foundation内存管理规则的Core Foundation内存管理函数的用法。
- (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); CGFloat locations[2] = {0.0, 1.0}; NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]]; [colors addObject:(id)[[UIColor lightGrayColor] CGColor]]; CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations); CGColorSpaceRelease(colorSpace); // Release owned Core Foundation object. CGPoint startPoint = CGPointMake(0.0, 0.0); CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds)); CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CGGradientRelease(gradient); // Release owned Core Foundation object. }
【工程转换时的常见问题】(Common Issues While Converting a Project)
当迁移已有工程时,可能你会遇到许多问题。这里列举一些常见问题,以及解决办法。
1.不能调用 retain, release,或 autorelease。
这是一个特殊点。你也不能写:
while ([x retainCount]) { [x release]; }
2.不能调用 dealloc
如果你实现了一个单例对象或在init方法中替换一个对象,你通常会调用 dealloc方法。对于单例对象,使用共享实例模式。在 init方法中,你不再需要调用dealloc,因为对象在你重写 self时会被释放掉。
3.不能使用 NSAutoreleasePool对象
使用新的 @autoreleasepool{}结构代替。这在你的自动释放池中强制使用block结构,并且比 NSAutoreleasePool要快六倍。即使在非ARC的代码中 @autoreleasepool也能工作,因为 @autoreleasepool较
NSAutoreleasePool要快得多,所以许多旧的”性能hacks“会无条件的被 @autoreleasepool所替代。
迁移器只处理 NSAutoreleasePool的简单用法,不能处理复杂条件下的情况,或者一个定义在新的 @autoreleasepool结构内部的变量并在其之外使用的情况。
4.ACR需要你将在init方法中 [super init]的结果赋值给 self。
下面的代码在ARC的init方法中是不合法的:
[super init];
简单的修正是做如下变换
self = [super init];
更全面的改法是在继续之前检测结果是否为nil:
self = [super init]; if (self) {
5.不能实现自定义的 retain和 release方法
实现自定义的 retain和 release方法会打破弱引用。有几种常见原因是想要提供提供自定义实现的:
性能
请不要再这么做了; NSObject的 retain和 release现在更加快捷。如果你仍发现问题,请提交bug。
实现自定义的弱指针系统
使用 __weak代替
实现单例类
使用共享示例代替。或使用类方法类替代实例方法,这避免了一直都要去分配对象空间的操作。
6."Assigned"实例变量成为了strong类型
在ARC之前,实例变量是非拥有引用(non-owning redernces)——直接将对象赋值给实例变量不会扩展对象的生命周期。为了令属性成为强类型,你通常只要实现或合成访问器方法,它们会调用合适的内 存管理方法;相比较而言,你可能已经如下例子中实现了访问器方法来维护一个弱类型属性。
@interface MyClass : Superclass { id thing; // Weak reference. } // ... @end @implementation MyClass - (id)thing { return thing; } - (void)setThing:(id)newThing { thing = newThing; } // ... @end
使用ARC,实例变量默认是强类型引用——将实例变量直接赋值给对象的确会扩展对象的生命周期。迁移工具没办法决定实例变量何时应该是 weak类型的。为了像之前那样维护同样的行为,你必须将实例变量标记为 weak类型,或使用一个声明式属性。
@interface MyClass : Superclass { id __weak thing; } // ... @end @implementation MyClass - (id)thing { return thing; } - (void)setThing:(id)newThing { thing = newThing; } // ... @end 或 @interface MyClass : Superclass @property (weak) id thing; // ... @end @implementation MyClass @synthesize thing; // ... @end
7.在C语言结构体重不能使用强类型的ids,例如,以下代码会编译不过
struct X { id x; float y; };
因为x默认肯定是被保留的,编译器无法安全的合成所有保证代码正常运转所需的所有代码。例如,如果你通过一些最终会被释放的代码来传入一个指针到这些结构 体后,每个id在结构体回收之前会被被迫释放掉。编译器不能可靠的做到这点,所以在结构体中的强类型的ids在ARC模式下是完全无效的。以下有几个可能 的解决办法:
(1)使用Objective-C对象代替结构体
这是最好的实践方法。
(2)如果使用Objective-C对象是次优方案,(可能你想要这些结构体组成的一个数组)那么考虑void *进行替代。
这需要使用显示的类型转换,在下面会提到。
(3)将对象引用标记为__unsafe_unretained
该方法对于一些不常见的模式可能会有效,如下:
struct x {NSString *S; int X;} StaticArray[]={ @"foo", 42, @"bar",97, };
这可能存在问题,并且如果对象在指针之外会被释放掉,这是不安全的,但实际对于像字符串常量一样一直存在的东西来说确实很有用的。
8.不能在 id与 void *(包括Core Foundation类型)之间进行直接转换
在 “Managing
Toll-Free Bridging.”中由详细的描述。
【常见问题】(Frequently Asked Questions)
我该怎样理解ARC?它把retians/releases放在哪里呢?
尝试不要再考虑retain/release应该放在哪,要考虑你应用的逻辑。考虑你对象中的”strong和weak“指针,对象的所有权,可能的循环引用。
我是否还需要为我的对象编写dealloc方法?
可能需要。
因为ARC不会自动操作malloc/free,Core Foundation对象的生命周期管理,文件描述,等等。你仍需要在dealloc方法中是否这样的资源。
你不需要(实际根本不需要)释放实例变量,但是你需要在系统类和其它未使用ARC编译的代码上调用[self setDelegate:nil]。
ARC中的dealloc方法不需要或不允许调用[super dealloc];到super的链式结构是在运行期进行处理和实施的。
在ARC中仍存在循环引用吗?
是的。
ARC自动处理retain/release,并且继承了循环引用的问题。幸运的是,迁移到ARC的代码很少内存泄露,因为属性(property)已经声明了是否要保留。
在ARC下我能创建C数组的保留指针不?
是的,可以!
// Note calloc() to get zero-filled memory. __strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(sizeof(SomeClass *), entries); for (int i = 0; i < entries; i++) { dynamicArray[i] = [[SomeClass alloc] init]; } // When you're done, set each entry to nil to tell ARC to release the object. for (int i = 0; i < entries; i++) { dynamicArray[i] = nil; } free(dynamicArray);
有些其它需要注意的地方:
某些情况下你需要使用__strong SomeClass **,因为默认是__autoreleasing SomeClass **.
分配的内存必须是零填充的
你必须在释放数组(memset或bzero无法工作)之前将每个元素设置为nil.
避免使用memcpy或realloc
ARC运行速度会慢吗?
这取决于你的测量方式,但基本”很快“。编译器高效的消除许多外部的 retian和 release调用,而且总的来说会更加关注于提升Objective-C运行时的速度。尤其是,当调用者是ARC代码时,常用的"返回一个 retain/autoreleased对象"模式会更快速而且实际上并没有将对象放入到自动释放池中。
我可以在特定文件下退出ARC吗?
可以。
在你迁移工程来使用ARC时,-fobjc-arc编译器标记默认会设置到每个Objective-C的源文件。你可以使用 -fno-objc-arc编译器标记来在特定的类中禁用ARC。在Xcode中,在target的 Build Phases中,打开 Compile Sources 组显示源文件列表。双击你想要设置标记的文件,在pop-up面板中输入-fno-objc-arc指定,之后点击完成。