arc 支持ios4 ios5

 

本文原址:https://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/_index.html#//apple_ref/doc/uid/TP40011226

或者访问:http://blog.csdn.net/nicktang/article/details/6884466

注:我原先在blog中的一篇翻译文章是beta版本的,原文已经有所更新,而且我原先没有翻译完全,因此我这里再次翻译一次。

由于本人英语水平有限,而且中文表达能力欠佳,很多地方不是很明白,所以肯定翻译的有错误,我会在以后有时间的时候再次验证这些地方。此外,Xcode4.2不是强制使用ARC,为了历史兼容性,还不建议在工程中使用ARC。

 

自动内存管理技术(Automatic Reference Counting (ARC))是一个为Objective-C提供内存自动管理的编译期技术。作为取代使用retain和release方式来管理内存的方式,ARC让你在其他代码编写方面可以放入更多精力。下图是两种管理内存方式的对比。

概要说明

ARC的原理是在编译期为每一个对象加入合适的代码,以期能否保证这些对象有合理的生命周期。从概念上来说,ARC通过增加retain,release和autorelease等函数,使得在维护内存计数器方面(相关资料Advanced Memory Management Programming Guide),达到和手动管理内存同样的效果。

为了达到产生正确代码的目的,ARC禁止一些函数的调用和toll-free bridging(相关资料)的使用。ARC也为内存计数器和属性变量引入了新的生命周期。

ARC在MAC OS X 10.6,10.7(64位应用),iOS4和iOS5中被支持,但是在MAC OS X10.6和iOS4中不支持弱引用(Weak references )。

Xcode提供一个能够自动转换工具,可以把手动管理内存的代码来转换成ARC的方式。你也可以为工程中的部分文件指定使用ARC,而另一部分指定为不使用。

你可以参考下面的资料:

ARC概述

作为不得不记得何时调用retain,releaseautorelease的替代,ARC会为你的每一个对象在编译期自动苹果,然后加入合适的函数调用来做内存管理,并且编译器会自动产生合适的dealloc函数。In general, if you’re only using ARC the traditional Cocoa naming conventions are important only if you need to interoperate with code that uses manual reference counting.(上面这句说什么?不懂,不翻译了)

一个Person类在使用ARC的情况下可能的实习如下:

@interface Person : NSObject
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSNumber *yearOfBirth;
@property (nonatomic, strong) Person *spouse;
@end
 
@implementation Person
@synthesize firstName, lastName, yearOfBirth, spouse;
@end

(关键字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能够起作用引入了一些新的规则。这些规则定义了所有的内存管理方方面面,某些规则是为了更好的体验,还有一些规则是为了减少编程人员在内存管理方面的工作。如果你违反这些规则,就会得到编译期的错误,而不是运行期的错误。

  • 你不能显示调用dealloc,不能实现和显示调用retain,release,retainCount和autorelease。

    当然也不能使用@selector(retain)@selector(release)等相关的功能。

    你可能为了管理某些不是实例释放方面的资源而实现一个dealloc。你不用(实际上是不能)释放实例变量,但是你可能需要为系统类实例调用[systemClassInstance setDelegate:nil],这些是ARC管不到的地方。

    在你实现的dealloc函数中不用调用[super dealloc] (实际上不行,因为会得到一个编译错误)。编译器会为父类的实例生成释放代码。

    你可以使用CFRetain, CFRelease或其他相关函数来管理核心功能的实例 (相关内容可以查看“Managing Toll-Free Bridging”).

  • 你不能使用NSAllocateObjectNSDeallocateObject.

    你使用alloc来创建实例;运行期间会管理这些实例的释放。

  • 你不能在C结构中使用实例指针。

    与其使用结构,建议你使用Objective-C类来管理数据。

  • id和void *不能进行隐式转换。

    你必须显式的告诉编译器这个转换的类型。关于这个方面,可以参考“Managing Toll-Free Bridging”.

  • 不能使用NSAutoreleasePool的实例.

    作为替代,ARC提供@autoreleasepool块作为替代。后者提供了更灵活的方式。

  • 你不能使用内存块。

    NSZone已经被废气,它已经给现在的Objective-C运行的时候忽落了。

为了和手动管理内存相兼容,ARC定义了函数和变量命名一条规则:

  • 属性变量的命名不能使用new开始。

ARC引入的新的生存周期修饰符

ARC为实例引入了几个新的生存周期修饰符,特别是自动nil化的弱引用。一个弱引用并不改变它所指向的实例的生命周期,自动nil化的弱引用会在实例被释放后自动变成nil。你应该妥善使用这些修饰符,ARC对于强引用(strong)类型提供很多内存管理,因此弱引用用的更多。

属性变量

新的关键词week和strong被引入,如下所示:

// 下面的同义词是: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
 
//下面的声明和"@property(assign) MyClass *myObject;"相似
//不过在MyClass的变量被释放的时候,
//这个属性变量的值被设置为nil,而不是一个僵尸指针。
@property(weak) MyClass *myObject;

变量修饰符

你可以使用下列关键词来修饰变量:

__strong
__weak
__unsafe_unretained
__autoreleasing

__strong是缺省的关键词。__weak声明了一个可以自动nil化的弱引用。__unsafe_unretained声明一个弱应用,但是不会自动nil化,也就是说,如果所指向的内存区域被释放了,这个指针就是一个野指针了。__autoreleasing用来修饰一个函数的参数,这个参数会在函数返回的时候被自动释放。

在对栈上分配的变量使用__weak修饰符的时候,必须加以注意。考虑下面的情况:

NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
NSLog(@"string: %@", string);

虽然这个NSString的变量初始化后立即赋值给string,但是由于没有其他强引用型的指针指向这个内存地址,所以这个内存地址立即就被释放了,所以后面的log语句显示的是一个空值。

在传值方面也需要注意,下面的代码是会有问题的:

NSError *error = nil;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
    // Report the error.
    // ...

 

但是error变量的声明类似于:

NSError * __strong e = nil;

函数的声明如下:

-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;

编译器对代码从新处理成下面这个样子:

NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
    // Report the error.
    // ...

这个错误存在于本地变量声明为__strong,而参数声明为__autoreleasing,编译器在这个情况下会生成一个临时变量。你可以声明这个本地变量为id __strong *,或者声明这个变量为__autoreleasing。

译者注:上面的这段大家再研究一下。我觉得有问题。

使用生存周期修饰符来避免强引用环

你可以使用这些生存周期修饰符来避免强引用环。例如,如果程序中存在父类,子类,并且父类需要还有指向子类的指针,那么就有可能存在这样的引用环,反之亦然,那么你可以让父类中的指针是强类型,子类中指向父类的指针是弱类型。其他的情况需要更精巧的处理,特别是在块代码中的使用。

在手动管理模式下,__block id x; 对x的内存计数加一。在ARC模式下,__block id x;缺省的对x的内存计数加一。为了是这样的代码能够在ARC模式下能够继续工作,你可以使用__unsafe_unretained __block id x;. 就像__unsafe_unretained名字暗示的那样,它也会有一定的问题 (因为它可能被称为野指针) 。有两种方式避免上面的问题,一种是使用__weak (如果不需要支持iOS 4或者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];
}];

下面是ARC模式的替代代码,在__block里面赋值为nil:

__block MyViewController *myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;
};

或者使用__weak修饰词,下面的代码就是一个演示:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
__weak MyViewController *weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};

在某些情况下,你可以使用:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
__weak MyViewController *weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;
    if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

在某些情况下,比如类不支持__weak模式,那么你可以使用__unsafe_unretained。但是你必须保证这个变量的生命周期,既保证它不能称为野指针。

ARC提供了新的语法来替代自动释放池

在ARC模式下,你不能直接使用NSAutoReleasePool来实现自动释放池,作为替代,ARC提供了新的语法:

@autoreleasepool {
     // Code, such as a loop that creates a large number of temporary objects.
}

这个新语句准许编译器管理自动释放的状态。

在进入点,自动释放池会压入栈中,当正常状态退出的时候,自动释放池会被处栈,(break, return, goto, fall-through等情况下)。但是如果在异常的情况下,自动释放池就不会有出栈。

这个语法在所有的Objective-C模式下都能工作。它比NSAutoReleasePool更有效率。你需要把所有原先的代码都改成新的模式。

ARC在Outlet变量方面的规定

这部分的规定只有一条,就是Outlet变量必须使用弱引用。

关于这个,请参考Resource Programming Guide中的“Nib Files”节。

栈上的变量被初始化为nil

使用ARC,栈上的变量,不论是强引用,还是弱引用,自动释放等类型的,都是会被隐式的初始化为nil,比如下面的代码:

- (void)myMethod {
    NSString *name;
    NSLog(@"name: %@", name);
}

上面的代码不会出现运行崩溃,但是会打印空值。

使用编译选项来开关ARC

使用-fobjc-arc打开ARC. 对于单个文件,你可以使用-fno-objc-arc来为某个你希望使用手动管理内存的文件来禁用ARC。

Xcode4.2再MAC OS X10.6和10.7(64位应用)和iOS 4,iOS5支持ARC,Mac OS X 10.6和iOS4不支持弱引用,Xcode4.1以及以前的版本不支持ARC。

管理无损桥接(Toll-Free Bridging)

Toll-Free Bridging,这个词太难翻译了,我找了很久也没有找到一个合适的翻译,大家就凑合着看吧

在很多Cocoa程序中,你会用到核心基础框架提供的类的实例,不论它是来自核心框架本身 (比如CFArrayRef 或CFMutableDictionaryRef)或者从核心基础框架延伸出的框架,比如核心图形框架(比如CGColorSpaceRefCGGradientRef)。

在ARC模式下,编译器并不制动管理这些核心功能类的实例,你必须自己调用CFRetainCFRelease (或者变量的特殊定义的函数)来符合核心基础的内存管理(参考 Memory Management Programming Guide for Core Foundation)。

如果你需要基础核心类实例和Objective-C类实例的转换,你需要使用一个转换(定义在objc/runtime.h)或者基础核心类型宏(定义在NSObject.h中)告诉编译器这个变量的所属关系。

  1. 如果是函数调用,请使用宏,比如CFBridgingRetain。这些宏使用新的修改算子来做id和void*之间的转换,并且告诉编译器关于这个void*的内存计数器的值。

    NS_INLINE CFTypeRef CFBridgingRetain(id X) {
        return (__bridge_retain CFTypeRef)X;
    }
     
    NS_INLINE id CFBridgingRelease(CFTypeRef X) {
        return (__bridge_transfer id)X;
    }

    仍然需要一个无操作运算符(__bridge)。

  2. 如果你使用C风格的转换,你可以使用直接转换:

    id my_id;
    CFStringRef my_cfref;
    ...
    NSString   *a = (__bridge NSString*)my_cfref;     // Noop cast.
    CFStringRef b = (__bridge CFStringRef)my_id;      // Noop cast.
    ...
    NSString   *c = (__bridge_transfer NSString*)my_cfref; // -1 on the CFRef
    CFStringRef d = (__bridge_retained CFStringRef)my_id;  // returned CFRef is +1

编译器处理从Cocoa函数返回的CF实例

编译器理解从核心功能函数返回遵循命名转换的Objective-C实例的从属关系 (参考Advanced Memory Management Programming Guide)。例如,编译器知道,在iOS中,从CGColor函数返回的CGColor是不从属于任何实例的。下面这个例子是一个演示:

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        CAGradientLayer *gradientLayer = (CAGradientLayer *)[self layer];
        gradientLayer.colors = [NSArray arrayWithObjects:[[UIColor darkGrayColor] CGColor],
                                                         [[UIColor lightGrayColor] CGColor], nil];
        gradientLayer.startPoint = CGPointMake(0.0, 0.0);
        gradientLayer.endPoint = CGPointMake(1.0, 1.0);
    }
    return self;
}

使用在函数参数转换的从属关键词

当在函数调用的参数中使用Objective-C和基础核心实例的转换的时候,你需要告诉编译关于这个传入参数的所属信息。这些基础核心实例的所属规则定义在基础核心内存管理规则中(参考 Memory Management Programming Guide for Core Foundation);Objective-C实例的规则定义在Advanced Memory Management Programming Guide.

在下面的代码片段中,传入函数的CGGradientCreateWithColors 的数组是需要转换的。byarrayWithObjects:函数返回的实例所属没有传入参数,转换关键词使用__bridge.

NSArray *colors = [NSArray arrayWithObjects:[[UIColor darkGrayColor] CGColor],
                                            [[UIColor lightGrayColor] CGColor], nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);

下例演示了函数中的代码片段的情况,注意所有核心内存管理函数都是遵循基础核心内存管理规则的。

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGFloat locations[2] = {0.0, 1.0};
    NSArray *colors = [NSArray arrayWithObjects:[[UIColor darkGrayColor] CGColor],
                                                [[UIColor lightGrayColor] CGColor], nil];
    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.
}

转换旧工程的要点

在扩展旧工程的时候,你需要注意下面事项.

你不能调用 retainrelease,或者  autorelease.

同样,下面的代码也不能写:

while ([x retainCount]) { [x release]; }
你不能调用dealloc.

一般情况下,在单例模式下,或者需要替换实例的时候,可能需要在init函数里面调用dealloc函数。对于单例模式,你可以使用共享模式来替代。因为后者在init里面不需要调用dealloc,在你从新对self赋值的时候,原内存就被释放了。

你不用使用NSAutoreleasePool对象

新的@autoreleasepool{}结构是作为一个替代。它比原先的NSAutoreleasePool快六倍。@autoreleasepool同样在手动管理内存的方式下能够工作。

在init方法中,ARC要求把[super init]赋值给self。

下面的代码在ARC模式下是不合法的。

[super init];

简单的改成下面这个样就可以了:

self = [super init];

一般还需要检查返回值:

self = [super init];
if (self) {
   ...
你不能实现自己的retain和release方法。

实现自己的retain或release方法会破坏弱指针,这里有一些理由阻止你这样做:

  • 性能.

    请不要实现这些函数,因为在NSObject中的实现快多了,如果你发现问题,那么需要改掉这些问题,而不是再次实现这些函数。

  • 为了实现弱指针To implement a custom weak pointer system.

    使用__weak来替代这些函数的实现.

  • 为了实现单例模式。

    使用共享模式。或者,使用类级别函数,这样根本不需要申请实例。

如果你发现不得不实现retain或者release函数,那么请在类中实现下面的函数:

-(BOOL)supportsWeakPointers { return NO; }

上面的函数阻止为你的类生成弱指针。不过还是强烈建议你使用其他的方式来避免实现这些函数。

不能在C语言的结构中使用strong  ids。

比如,下面的代码不能被编译:

struct X { id x; float y; };

因为x并定义强引用,那么编译器就不能为它生成按照的管理代码。例如,如果x以及被赋值,那么在这个结构的指针被free前,必须先释放这些x指向的内存,但是由于是ARC模式,所以这些内存就被泄漏了,所以在C语言的结构中,ARC禁止使用强指针,下面是可能的解决方案:

  1. 使用类来代替结构。

    这是最好的方式。

  2. 如果在使用类有性能问题(比如你需要一个很大的结构的数组),那么可以使用void*来代替。

    这需要显式的转换,下面会有讨论。

  3. 把这个变量用__unsafe_unretained修饰。

    这个会对下面的情况会有帮助:

    struct x { NSString *S;  int X; } StaticArray[] = {
      @"foo", 42,
      @"bar, 97,
    ...
    };

    你可以这样定义结构:

    struct x { __unsafe_unretained NSString *S; int X; }

    这样的写法可能会有很大的问题,如果在指针里面的实例被释放了。but it is very useful for things that are known to be around forever like constant string literals.

你不能直接进行id和void *的转换(包括核心功能中的类型)。

这个问题的细节在 “Managing Toll-Free Bridging”里面讨论。

常见问题解答

ARC的机制是什么?它在那里放入retain/release函数调用?

请停止思考这些问题,把更多的精力放在下面的问题上,比如你的程序逻辑,对象的强,弱引用,对象的所属关系,可能的循环引用等问题上。

我还需要为我的类写一个dealloc函数么?

可能。

因为ARC并不自动调用malloc/free,并不自动管理像核心功能对象、文件描述符等的生命周期,所以你仍然需要在dealloc中释放它们。

你不会(实际上是不能)释放对象实例,但是你仍然需要为系统类调用[self setDelegate:nil]或其他ARC不会自动生产释放代码的情况下释放资源。

dealloc方法在ARC不是必须的,并且不许调用[super dealloc]。

ARC中会出现循环引用(retain cycles)么?

是的。

ARC自动产生retain/release函数,同时也继承了产生循环引用的机制。幸运的是,ARC很少有内存泄漏,因为在声明属性的变量的时候决定是否使用retain。

注:请搜索什么是retain cycles.

ARC下面的块代码如何工作?

块代码在ARC方式下,只能在栈上传递的方式下工作,比如在return语句中。你不用在调用Block Copy。你还是必须在为arrayWithObjects: 传入栈的时候使用[^{} copy]和其他的retain功能的函数。

有一件事情需要提到,在ARC方式下,__block NSString *S是可以使用,它不是一个野指针。使用_block __unsafe_unretained NSString *S 或者 (更好的方式) __block __weak NSString *S.

我能在雪豹(Snow Leopard)上的XCode为MAC OS X开发应用的时候使用ARC么?

不能,那个版本的Xcode4.2不支持ARC。雪豹版本没有10.7的SDK,所以在为MAC OS X开发应用的时候不能使用ARC,但是这个版本支持为iOS开发的时候使用ARC。狮子(Lion)版本的是可以的。

在支持ARC的工程中我能创建实例数组么?

是的,你可以,如下例所示:

// 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 **.

  • 申请的内存必须使用0填充。

  • 在释放这个数组前,你必须设置每一个元素为nil(调用memset,并传入0是没有用的)。

  • 你必须避免使用memcpy或realloc。

ARC慢么?

这个取决你如何衡量这个问题了,不过一般来说,这个问题的答案是“no”。下面是原文的解释,我总结的是要相信编译器在这个方面的能力。It depends on what you’re measuring, but generally “no.” The compiler efficiently eliminates many extraneousretain/release calls and much effort has been invested in speeding up the Objective-C runtime in general. In particular, the common “return a retain/autoreleased object” pattern is much faster and does not actually put the object into the autorelease pool, when the caller of the method is ARC code.

One issue to be aware of is that the optimizer is not run in common debug configurations, so expect to see a lot more retain/release traffic at-O0 than at-Os.

在ObjC++模式,ARC能否工作?

式的,你甚至可以在类或者容器中使用strong/weak id。为了能正常工作,ARC在编译期间会在复制构造函数和析构函数中加入retain/release方面的逻辑。一个事情需要避免的就是,你不能为某些指针使用__strong,例如:

std::vector<__strong NSString*> V;

那些类不支持自动nil化的弱引用?

下面的类的实例不能使用自动nil化的弱引用

NSATSTypesetterNSColorSpaceNSFontNSFontManagerNSFontPanelNSImage,NSMenuView,NSParagraphStyle,NSSimpleHorizontalTypesetter,NSTableCellView,NSTextView,NSViewController,NSWindow, andNSWindowController. 还有,OS X上的AV Foundation框架中所有类都不支持自动nil化的弱引用。

如果属性变量是这些类的实例的时候,使用assign来替代weak;作为变量,使用__unsafe_unretained来替代__weak。

此外,你也不用对NSHashTable,NSMapTable, 和NSPointerArray的实例做弱应用。

 

在写类似于NSCell这样使用了NSCopyOjbect的子类的时候有什么需要特别关注的地方么?

没有什么特别的,ARC会做一切事情。

我可以为每一个文件单独指定是否使用ARC么?

是的。

当你为一个旧的工程添加ARC是,编译选项-fobjc-arc对所有的文件都是有效的。你可以使用编译选项-fno-objc-arc为某个类单独禁止ARC。在target上的Build Phases 页,打开Compile Sources组,这里有一个文件列表,双击某个文件,加入-fno-objc-arc就可以为这个文件禁用ARC。

GC (Garbage Collection)是否被废弃了?

GC在10.7系统中被支持,但是强烈建议在新工程中使用ARC,对于你老的代码,你可以做一下转换,比较工作量不大。

我的话费充值店-各种面额
电信100元仅售98.60 

联通100仅售99.00
移动100仅售99.30

你可能感兴趣的:(ios)