您的位置:首页 > Web前端

如何在项目中开始使用ARC(Automatic Reference Counting)

2014-01-05 22:46 525 查看
Automatic Reference Counting (ARC)是编译器自动管理Objective-C对象的一个功能,相对于不得不考虑retain和release操作来说,ARC让我们有更多的精力集中在我们应用内有趣的代码、object graphs和对象之间的关系上。



概要

ARC是用过来在编译的时候添加适当的代码来保证对象在有用的时候有效,没有了就不再有效了。从概念上讲,ARC通过调用合适的内存管理方法遵循着和 manual
reference counting(MRC)同样的内存管理规则。
为了让编译器产生正确的代码,ARC严格规定了你可以调用的方法和怎样使用toll-free
bridging。ARC引入了新的用于对象引用和属性声明的生命周期标识符。
ARC支持Xcode
4.2 for OS X v10.6 and v10.7 (64-bit applications) and for iOS 4 and iOS 5。弱引用不支持OS X v10.6 and iOS 4。
Xcode提供了一个工具用来把非ARC代码自动转换到ARC代码(例如移除retain和release的调用),解决的不能自动迁移的问题。这个工具会把工程中的所有文件都转换成ARC,在使用非ARC方便的情况下,你也可以选择某些文件使用ARC。

ARC概况

不用再记住什么时候该使用retain、release和autorelease了,ARC知道你的对象的使用周期然后在编译的时候加入了合适的内存管理代码。编译器也会为你生成合适的dealloc方法。如果你刚刚使用ARC,那么传统的Cocoa的命名规范在你管理MRC的代码的时候是非常重要的。
一个完整的正确的Person类的实现是这样的:

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@property NSNumber *yearOfBirth;
@property Person *spouse;
@end

@implementation Person
@end
对象的属性默认是strong的。

使用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提出了一些新的别的编译器没有的新规则。这些规定是为了能够提供一个完整的可靠的内存管理模型。在一些情况能够很简单的提升性能,在一些情况下能够简化代码或者让你不用处理内存管理。如果你违背这些规则,在编译的时候就会出错,而不是在运行的时候产生错误。

你不能显示的调用dealloc,实现或者调用retain、release、retianCount或者autorealease。
             禁止使用 
@selector(retain)
@selector(release)等等

             如果你想要管理资源而不是释放实例变量,你可以实现一个dealloc方法。你没必要也不能释放实例变量,但是你可以调用系统类或者非ARC代码的                                                 [systemClassInstance setDelegate:nil]方法。
             在ARC下实现的dealloc方法不能调用[super dealloc]方法,会导致编译错误。对于父类的调用是由编译器自动和强制实现的。
             你仍然可以调用CFRetian、CFRelease和一些与Core Foundation-style对象有关的方法。

你不能使用
NSAllocateObject 和
 
NSDeallocateObject

             你用alloc来创建对象,Runtime系统来负责释放对象。

在C结构体中不能使用对象指针。
             代替结构体,你可以使用一个Objective-C的类来管理数据

不能随便的转换id和void*
             你必须使用特殊的转换来告诉编译器对象的声明周期,你必须在Objective-C对象和作为函数参数的Core Foundation类型之间这么转换。看下面的Toll-Free-Bridging。

你不能是用NSAutoreleasePool
              ARC提供了@autoreleasepool块来代替,比NSAutoreleasePool有更高的效率。

你不能是用内存zones
              没有必要再使用NSZone,它已经被Objective-C运行时系统忽略了。

        为了能够和手动管理内存的代码交互,ARC提出了严格的命名规范。

不能提供以new开头的获取方法,这意味着你不能添加一个以new开头的属性,而不自己添加get方法。
// Won't work:
@property NSString *newTitle;

// Works:
@property (getter=theNewTitle) NSString *newTitle;

ARC提出了新的生命周期标识符

ARC提出了几个生命周期标识符和弱引用。弱引用不会延长它指向的对象的生命周期。当没有强引用指向这个对象的时候弱引用就会被赋值为nil。
你应该在你的应用内充分利用这些标识符来管理object graph。ARC不能防止循环引用的发生,但是我们可以明智的使用弱引用来避免循环引用发生。

Property属性

weak和strong可以用作Preperty的属性。就像下面这样:
// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;

// The following declaration is similar to "@property(assign) MyClass *myObject;"
// except that if the MyClass instance is deallocated,
// the property value is set to nil instead of remaining as a dangling pointer.
@property(weak) MyClass *myObject;
在ARC下,strong是默认的对象类型。

变量标识符

你可以像使用const一样使用下面的变量标识符:
__strong
__weak
__unsafe_unretained
__autoreleasing

__strong是默认的,一个对象只要有__strong类型指针指向它,它就有效。
__weak声明了一个引用并不持有该对象。当没有强引用指向弱引用指向的对象的时候,弱引用被赋值为nil。
__unsafe__unretain声明了一个不持有对象的引用。当没有强引用指向该对象的时候,该引用不会被赋值为nil,所以当一个该引用指向的对象被回收的时候,该引用是危险的。
__autorelease是用来声明通过id*传递并返回autorelease的参数的。
你应该正确的使用标识符,当声明一个对象的变量的时候正确的做法如下:
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是在初始化赋值之后使用的,但是在赋值的时候没有强引用指向string,因此它立刻就会被释放。NSLog语句会输出一个null值(在此例子中编译器会给出警告)。
你同样要注意通过引用传递的对象,下面的代码是没有问题的:
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// Report the 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) {
// Report the error.
// ...

局部变量类型(__strong)和函数参数类型(__autorelease)不匹配,就会引起编译器生成一个临时变量。当你使用__strong变量地址的时候你可以通过声明参数为__strong来获得原始指针。另外你也可以声明为__autorelease。

使用生命周期标识符避免循环引用

你可以使用生命周期标识符来避免循环引用,例如,典型的你有这么一个父子对象关系,父亲需要引用他的孩子,反之亦然,然后你创建一个父亲到孩子的强引用关系和孩子到父亲的弱引用关系。其他的情况可能更微妙,特别是涉及到block objects的时候。
在MRC手动引用计数模型下,__block id x;对x不会有retaining影响。但是在ARC下,__block id x;默认的retaining x就像其他变量一样。为了在ARC下获得和MRC一样的效果,你可以使用__unsafe__unretained __block id x;就像__unsafe__unretained名字表明的一样,拥有一个non-retianed的变量是危险的,因此这个是不提倡的。两个比较好的解决方案是使用__weak(如果你不需要支持iOS4或者OS X v10.6),或者设置__block变量为nil来打破循环引用。
下面的代码片段说明了一个在MRC下有时会用到的模式:
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];
};
没有了循环引用,但是你应该这样用:
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.
}
这个简单的结构可以允许编译器来推断引用计数状态。在入口处,一个autorelease pool被放进栈。在退出的地方(break,return,goto,fall-through等等)autorelease pool被弹出栈。为了兼容已经存在的代码,如果由于异常退出,autorelease pool是不会被弹出栈的。
这个语法是在所有的Objective-C模型中都可以用。这个是比NSAutoreleasePool更加有效率的。因此你应该在使用NSAutoreleasePool的地方使用它。

Patterns for Managing Outlets Become Consistent Across Platforms(管理Outlets)

在ARC下,iOS和OS X 中outlets的声明改变了,变成了跨平台一致的了。你应该使用的典型的模型是:outlets应该是weak的,除了来自nib文件top-level对象的File`s Owner,应该是strong。

栈变量初始化为nil

使用ARC,strong,weak和autoreleasing 栈变量现在会被隐式的初始化为nil。例如:
- (void)myMethod {
NSString *name;
NSLog(@"name: %@", name);
}
NSLog语句将会输出null而不是crashing。

使用编译标志来开启和关闭ARC

你可以使用-fobjc-arc编译标志来开启ARC。如果一些文件使用MRC更方便的话,你可以选择按文件来使用ARC。对于默认开启ARC的项目来说,你也可以使用-fno-objc-arc来指定某些文件不适用ARC。
ARC支持Xcode4.2和OS X v10.6之后的64位应用,或者iOS4之后的。弱引用不支持OS X v10.6和iOS4,所以在Xcode4.1之前都不支持ARC。

管理Toll-Free-Bridging

在许多的Cocoa应用中,你需要用到Core Foundation-style的对象,这些对象可能来自于Core Foundation框架本身例如CFArrayRef或者CFMutilDictionaryRef,或者是用来在Core Foundation 和 Core Graphics之间进行适配的框架例如CGColorSpaceRef和CGGradientRef。
编译器不能够自动管理Core Foundation对象的生命周期。你必须调用Core Foundation内存管理规则声明的的CFRetain和CFRelease函数。
如果你想要在Objective-C对象和Core Foundation对象之间做转换,你需要通过定义在objc/runtime.h中的转换或者定义在NSObject.h中的Core Foundation-style宏告诉编译对象的持有关系。

__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对象

编译器知道返回CF对象的Cocoa方法遵守着历史Cocoa命名规约。Core Foundation-style对象的持有关系详细的阐述在CF内存管理规则里面。
下面的代码片段,传给CGGradientCreateWithColors方法的数组需要合适的转换。arrayWithObjects:返回的对象的持有关系没有传递给该方法,所以仅需要__bridge。
NSArray *colors = <#An array of colors#>;
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
在下面的方法实现中的代码片段,可以注意下CF内存管理规则声明的内存管理方法的使用。
- (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.
}

转换工程可能遇到的问题

在转换存在的工程的时候你可能遇到各种问题,这里列出了一些问题和答案。

你不能调用retian、release或者autorelease
              这样会失败,你也不能这样写:
while ([x retainCount]) { [x release]; }

你不能调用dealloc
              如果你实现单例或者实现了一个类的init方法,通常会调用dealloc。你不能再在init方法中调用dealloc,因为当你覆盖self的时候,这个对象会被释放。

不能再使用NSAutoreleasePool对象
             代替使用@autoreleasepool{}块。在autorelease pool上的块结构的速度是NSAutoreleasePool的6倍。@autoreleasepool可以工作在非ARC下。正是因为                                    @autoreleasepool比NSAutoreleasePool快很多,所以许多老的性能问题可以无条件的用@autoreleasepool来替换。项目转换只能解决简单的NSAutoreleasePool的
               使用,不能处理复杂的情况,或者是变量定义在@autoreleasepool内,然后在之后使用的情况。

ARC需要你在init方法中把[supre init]的结果赋值给self。
              下面的代码在ARC下是无效的:

[super init];
              简单的修改如下:
self = [super init];
              更好的处理是,检查self是否为空之后再继续操作:
self = [super init];
if (self) {
...

(以下红字部分为自己添加)

init开头的方法要返回实例对象
id obj = [[NSObject alloc] init];

init方法会初始化alloc方法返回的对象,然后原封不动的返回给调用者。
- (void)initThisObect;

这个方法声明就不对。只有一个方法例外:
- (void)initialize;


属性声明为readonly
属性声明为readonly,而没有添加任何其他方法的话,不管是在类内还是类外都无法对该属性赋值。
@property (nonatomic,readonly,strong) NSString *str;

属性会自动生成一个成员变量_str,一般情况下会生成该成员变量的get和set方法,但是readonly表示只生成了get方法而没有生成set方法,如果我们想在自己的类内对该属性进行赋,我们可以通过以下两种方式声明私有的set方法,然后我们就可以在自己的类内对readonly属性就行赋值啦。
在类的扩展中重新声明一个readwrite属性,只要名字相同该属性同样会对应到_str成员变量:
@property (nonatomic,strong,readwrite) NSString *str;

声明一个私有的set方法:
- (void)setStr:(NSString *)tempStr
{
if (tempStr != _str)
{
_str = tempStr;
}
}

你不能实现retain或者release方法
              自己实现retain和release方法破坏了弱指针,在以下几种情况下可以实现自己的方法:

性能。不要再这么做了,因为NSObject的retain和release的实现现在已经非常快了。如果你任然发现问题,请提交给苹果Bug。
自己实现弱指针。请使用weak。
单例类的实现。请使用单例模式,使用类方法代替实例方法,那样就避免了不得不申请对象。              

“Assigned”变量变strong
              在ARC之前,实例变量是没有持有关系的,直接赋值给实例变量是不会延长对象的生命周期的。声明一个strong属性,会实现或者同步一个调用了合适的内存管理方法                 的获取方法。相反,你可能实现过下面这样的代码来维护弱指针。

@interface MyClass : Superclass {
id thing; // Weak reference.
}
// ...
@end

@implementation MyClass
- (id)thing {
return thing;
}
- (void)setThing:(id)newThing {
thing = newThing;
}
// ...
@end
在ARC下,实例变量默认是强引用。赋值给实例变量会延长对象的生命周期。转换工具不知道实例变量是否为弱引用。为了能够拥有和之前一样的功能,你需要使用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结构体中不能使用ids。

例如下面的代码不能编译:

struct X { id x; float y; };
因为X默认是请持有的,编译器不能安全的实现所有使之能正确运行的代码。例如通过一些代码传递指针给这些结构体的一些然后结束的时候释放。每一个id不得不在struct释放之前释放。编译器不能依靠这个,所以在ARC下结构体内禁止使用强id。有一下几个解决方案:

使用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*之间进行转换

一些常问的问题(不译)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息