什么是Automatic Reference Counting?
Automatic Reference Counting(ARC)是一个编译器特性,它为Objective-C对象提供自动内存管理机制。相比手动地retain/release,ARC让你专注于其它代码,而不需要再考虑retain/release。下图显示了ARC和非ARC的区别。
在手动引用计数模式下,
__block id x;
可以不retainingx
。在ARC模式下,__block id x;
默认会retainingx
(就像其它变量一样)。为了在ARC中获得手动引用计数一样的效果,你可以使用__unsafe_unretained __block id x;
。像它的名字一样,有一个non-retained对象是很危险的(因为它可能变成野指针),因此不鼓励这么做。两种不同的选择是使用__weak
(如果你不需要支持iOS 4或OS X v10.6),或者把这个__block
值设置成nil来打断retain cycle.下面的代码片段使用手动引用计数有时使用的一个模式来说明这个问题。
MyViewController *myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
[myController release];
}];
像上面讲的那样,你可以用一个__block
限定符,然后在completion handler
把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];
};
前面讲到weak指针,没有其它strong指针指向对象的时候会自动置为nil,为了保证block执行期间对象不被释放(可能在其它线程中被释放),你应该这么做:
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使用新的语句管理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
等等)自动释放池被pop。为了兼容现有的代码,如果因为一个异常退出,自动释放池不会被pop。
这个愈发在所有Objective-C模式下都支持。它比NSAutoreleasePool
类效率更高;鼓励用它来代替NSAutoreleasePool
的位置。
平台间定义Outlets的模式变得一致
在iOS和OS X中使用ARC定义outlets的模式变得一致了。你应该遵循的模式是:outlets应该是weak,除了那些nib文件(或者storyboardscene)中顶层的对象应该是strong。
详细的信息可以查看Resource Programming Guide中的Nib Files。
栈变量初始值为nil
使用ARC时,strong
、weak
和autoreleasing
栈变量隐式地用nil初始化。例如:
- (void)myMethod {
NSString *name;
NSLog(@"name: %@", name);
}
不会崩溃,而是打印name
的值为null
。
使用编译Flags来启用或关闭ARC
你用-fobjc-arc编译标志来开启ARC。如果大部分使用手动引用计数更方便的话,你可以选择以文件为单位开启ARC。对于那些开启了ARC的工程,你可以用-fno-objc-arc编译标志来给指定的文件关闭ARC。
ARC支持Xcode4.2以上,OS X v10.6以上,iOS4.0以上。Weak引用不支持OS X v10.6和iOS 4。Xcode 4.1和之前的版本不支持ARC。
处理Toll-Free Bridging
在许多Cocoa应用中,你需要用到Core Foundation风格的对象。不管你是直接只用Core Foundation框架(比如CFArrayRef
或CFMutableDictionaryRef
),还是使用集成了Core Foundation
框架的一些框架,比如Core Graphics框架(你可能用到CGColorSpaceRef
和CGGradientRef
)。
编译器不会自动处理Core Foundation对象的生命周期;如Core Foundation内存管理规则(查看Memory Management Programming Guide for Core Foundation)里所述,你必须调用CFRetain
和CFRelease
(或者其它相关的变体)。
如果你在Objective-C和Core Foundation对象之间转化,你必须要告诉编译器怎么处理对象的生命周期,你可以使用类型转换限定符(objc/runtime.h
里定义的)或者Core Foundation宏(NSObject.h
里定义的)。
__bridge
让对象在Objective-C和Core Foundation之间转换,但是并不转换所有权。
__bridge_retained
或者CFBridgingRetain
把一个Objective-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对象
编译器理解遵循Cocoa命名规则(查看Advanced Memory Management Programming Guide),并返回Core Foundation类型的Objective-C方法返回。例如,编译器知道,在iOS中,UIColor
类的CGColor
方法返回的CGColor
对象不会被拥有。但是你还是需要使用正确的类型转换,如下面的例子所示:
NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
[colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
使用所有权关键字转换函数参数
当你在函数调用时在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.
}
#转化工程到ARC时的常见问题
当迁移已有工程的时候,你可能会遇到许多问题。这里是一些常见的问题和解决方法。
-
你不能调用
retain
、release
或者autorelease
。
这是一个特性。你也不能这么写:
while ([x retainCount]) { [x release]; } -
你不能调用
dealloc
。
典型地,如果你定义一个单例或者在init
方法中替换一个对象时,你要调用dealloc
。对于单例,使用共享实例模式。在init
方法中,你不需要再调用dealloc
方法,因为当你覆盖self的时候对象会自动释放。 -
你不能使用
NSAutoreleasePool
对象。
使用心得@autoreleasepool{}
结构来代替它。它在你的自动释放池上包装了一个块结构,并且会比NSAutoreleasePool
快上6倍。@autoreleasepool
在非ARC代码下也能工作。因为@autoreleasepool
比NSAutoreleasePool
快很多,所有许多“性能问题”可以无脑地把NSAutoreleasePool
替换为@autoreleasepool
。
迁移工具可以处理简单的NSAutoreleasePool
,但是不能处理复杂情况下的,也不能处理在@autoreleasepool
块里定义变量,然后在后面才使用这个变量的情况。 -
** ARC需要你在
init
方法中把[super init]
的返回值赋给self
。
下面的init
方法在ARC**中是非法的:
[super init];
简单的修改一下:
self = [super init];
完整的修改方法是像上面那样做,并在继续前检查结果是nil:
self = [super init];
if (self) {
... -
你不能自定义
retain
或者release
方法。
自定义retain
或者release
方法来实现weak
指针。你可能因为一些常见的原因想要自定义: -
性能.
请不要在这么做;现在NSObject
类的retain
和release
方法比以前更快了。如果你还是发现问题,请提交bug。 -
定义weak指针系统
使用__weak
来代替。 -
定义单例。
使用共享实例模式来代替。或者,使用类方法代替实例方法,可以完全避免分配对象。 -
"Assigned"实例变量变成
strong
。
在ARC之前,实例变量不拥有对象-直接给实例变量赋值一个对象不会保持对象的生命周期。为了使属性变成strong
,你通常可以定义或者synthesized
存取器方法来调用合适的内存管理方法;形成鲜明地对比,你可能像下面例子中展示的那样,定义了一个存取器方法来获得一个weak属性。
@interface MyClass : Superclass {
id thing; // Weak reference.
}
// ...
@end@implementation MyClass - (id)thing { return thing; } - (void)setThing:(id)newThing { thing = newThing; } // ... @end
使用ARC,实例变量默认是strong
引用-直接给实例变量赋值一个对象会保持对象的生命周期。迁移工具不能决定什么时候实例变量是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
- 你不能在C结构题中使用
strong
对象。
例如下面的代码不能通过编译:
struct X { id x; float y; };
这是因为x默认是strong
,编译器不能安全地合成让它能正确工作的代码。例如,如果你传了一个指向那种结构体的指针到处理释放的代码中,每个id
都会在结构体释放前释放。编译器做这些是不可靠的,所以在ARC模式下结构题中strong
对象是不允许的。有一些解决方案:- 使用Objective-C来代替机构提。
这被认为是最好的方案。
- 使用Objective-C来代替机构提。
- 如果使用Objective-C是次级方法,(可能你想使用这些结构体的数组)你可以考虑使用
void *
来代替。
这需要使用显示的转换,如下面所述。 - 把对象引用弄成
__unsafe_unretained
。
这个方法在像这样的并不常见的模式中可能有用:
struct x { NSString S; int X; } StaticArray[] = {
@"foo", 42,
@"bar, 97,
...
};
你像这样定义结构题:
struct x { NSString * __unsafe_unretained S; int X; }
这样做可能会出问题,并且是不安全的,因为对象可能会被释放,但是对于那些永久存在的对象可能非常有用,比如字符串常量。
你不能在id
类型和void*
类型之间直接转换(包括Core Foundation类型)。
这些在前面的处理Toll-Free Bridging*中已经讨论过。
常见问题
我怎么理解ARC?它在哪里插入retain/release?
不要去考虑retain/release在哪里插入的,去考虑你的程序的算法。考虑你的对象的"strong and weak"指针,考虑对象的拥有关系,考虑哪里可能会retain cycles。
我仍然需要给我的对象写dealloc
方法吗?
可能需要。
因为ARC不会自动malloc/free
,不会管理Core Foundation对象和file descriptors的生命周期,等等。你还是需要写一个dealloc
方法来释放这些资源。
你不需要去释放实例变量,但是你可能需要给系统类和其它没有使用ARC编译的代码调用[self setDelegate:nil]
。
ARC仍然可能发生retain cycles吗?
是的。
ARC自动retain/release,并继承了retain cycles问题。幸运的是,迁移到ARC的代码很少发生内存泄漏,因为不管指针的属性是不是retain,都已经自动合成了释放代码
block在ARC里怎么工作的?
在ARC模式下,你仅仅需要在栈上传block,它们就能工作了。你不再需要调用Block Copy。
有一点需要注意,NSString * __block myString
在ARC模式下会retain对象,而不是一个可能会变成野指针的指针。为了获得以前一样的效果,使用__block NSString * __unsafe_unretained
或使用__block NSString * __weak myString
(这种更好)。
我可以在OS X雪豹上用ARC开发吗?
不行。雪豹Xcode4.2不支持ARC,因为它不包含10.7SDK。雪豹Xcode 4.2支持iOS上的ARC,LionXcode 4.2OS X和iOS都支持。这意味着,你需要在Lion系统上buildARC应用,然后在雪豹系统上运行。
在ARC下我可以创建一个retained指针的C数组吗?
是的,你可以,下面例子里说明了怎么做:
// Note calloc() to get zero-filled memory.
__strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(entries, sizeof(SomeClass *));
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慢吗?
它取决于你测量什么,但是一般情况下是“不会”。编译器有效地消除了许多retain/release调用,而且通常Objective-C runtime为ARC的速度作了很多优化。特别是,那些返回一个retain/autoreleased对象的模式会更快,因为当调用方法的代码是ARC模式下,对象不会被放进自动释放池。
一个需要注意的问题是在通常的debug配置下优化程序不会运行,因此在-O0下比-Os下可以看到更多retain/release。
ARC在ObjC++模式下能工作吗?
是的。你也可以把strong/weak对象放在类和容器里面。ARC编译器在拷贝构造函数和析构函数中自动生成retain/release逻辑。
哪些类不支持weak引用?
目前你不能创建下面类的weak引用:
NSATSTypesetter, NSColorSpace, NSFont, NSMenuView, NSParagraphStyle, NSSimpleHorizontalTypesetter, and NSTextView.
注意:另外,在OS X v10.7下,你不能创建NSFontManager, NSFontPanel, NSImage, NSTableCellView, NSViewController, NSWindow, 和 NSWindowController的weak引用。还有,在OS X v10.7下AVFoundation框架下的类都不支持weak引用。
定义属性的时候,你应该用assign
来代替weak
;对于变量,你应该使用__unsafe_unretained
代替__weak
。
另外,在ARC下你不能创建NSHashTable,NSMapTable,或NSPointerArray的weak引用。
当子类化NSCell或其它类时使用NSCopyObject
我需要怎么做?
没有特别需要注意的。ARC代替你来处理以前需要额外显示地retain的地方。在ARC下,所有的copy方法应该只在实例变量上copy。
我能指定部分文件用ARC编译吗?
可以。
当你迁移一个工程到ARC,默认所有的Objective-C文件都被设置了-fobjc-arc编译标志。你可以用* -fno-objc-arc来为部分类禁用ARC。在Xcode里,在target的Build Phases标签,打开Compile Sources组来
浏览源文件列表。双击你想设置的文件,在弹出的框中输入-fno-objc-arc
,然后点击Done*。
GC(垃圾回收)在Mac被弃用了吗?
垃圾回收在OS X Mountain Lion v10.8中被弃用了,并且将会在将来的OS X版本中被删除。自动引用计数是推荐的替换技术。为了帮助迁移已有的应用,Xcode 4.3和之后的ARC迁移工具支持把OS X程序中的垃圾回收迁移到ARC。
注意:对于Mac App Store上的应用,苹果强烈推荐尽可能地用ARC替换垃圾回收机制,因为Mac App Store指南(查看App Store Review Guidelines for Mac Apps)禁止使用弃用的技术。
网友评论