《iOS应用程序开发方法与实践》补充内容-2.14 自动引用计数ARC

来源:互联网 发布:360数据恢复大师官网 编辑:程序博客网 时间:2024/05/10 13:52

说起轿车,人们更青睐自动挡轿车,因为在开车过程中不用再去注意离合器和挂档,只需要控制油门和制动就可以了,这符合KISS原则。

对于iOS开发中的内存管理,在《iOS应用程序开发方法与实践》一书的第二章中介绍了手动管理内存的原则和若干注意事项(参见2.5.4节)。手动管理内存不仅麻烦(什么时候该保留、释放、自动释放),而且特别容易出错(内存泄漏、访问僵尸对象)等。正如自动挡汽车广受欢迎一样,iOS 5 SDK引入了自动引用计数(Automatic Reference Counting,简称ARC),它能够自动替开发者进行对象的保留、释放操作,让开发者更关注于程序逻辑,无需为内存管理的技术细节分心。


一、什么是ARC

ARC是LLVM编译器提供的一个新的特性,能够根据代码自动在指定位置插入保留、释放操作,而无需程序开发者手动进行保留和释放。从这一点上不难发现,ARC在本质上与其他编程语言中的垃圾回收技术(Garbage Collection,简称GC)是完全不同的。ARC是编译器在编译过程中进行的额外操作,在指定位置插入保留、释放操作代码;而GC则是运行时进行的操作,Java的JVM以及.NET的CLR会在程序运行过程中根据一定的算法识别出无用对象并进行清理。因此,当启用了ARC后,代码同样会遵循内存管理原则,只不过很多代码是由编译器代劳而已,此时你就不用(也不能)再去调用retain、release、autorelease等方法了。


二、如何启用ARC

在Xcode创建项目时,选中对话框下方的“Use Automatic Reference Counting”复选框。或者在项目配置的Build Settings中,将LLVM中的"Objective-C Automatic Reference Counting"置为Yes即可。


三、ARC中的对象指针

在Objective-C中,每个对象保存在内存中的一段空间内,程序通过对象的首地址来访问对象。对象指针就是一个变量,用于保存对象的地址。

ARC中的指针分为强指针和弱指针。强指针在当进行对象地址的赋值操作时会保留新值并释放旧值,当其退出指针作用域时释放。而弱指针则类似于手动内存管理时的指针,它仅仅是一个指针而已,不做额外的保留和释放,在对象被回收时会自动置为nil。在程序中使用__strong关键字指定强指针,使用__weak关键字指定弱指针,默认为强指针。

在具体介绍之前,不妨先上代码:

    if (someCondition)    {        NSMutableArray* stringArray = [NSMutableArray arrayWithObjects:@"A", @"B", @"C", nil];        id firstObj = [stringArray objectAtIndex:0];        [stringArray removeObjectAtIndex:0];        NSLog(@"%@", firstObj);    }

if语句块的第一行代码创建了一个数组,其中包含三个字符串对象,然后将其地址赋值给stringArray。第二行将数组的首元素地址赋值给firstObj,即指向字符串@"A"。第三行将数组首元素移出数组。第四行打印firstObj的描述信息。最后退出if语句块。

在未开启ARC时,由于元素在被移出数组时会被数组释放,所以在调用NSLog函数时@"A"已经被回收,firstObj指向的是一个无效的地址(野指针、僵尸),这会导致程序崩溃。

而当开启了ARC时,上述代码就是正确的。当执行到第二行时,stringArray和firstObj均为强指针(默认为强指针),分别保留数组和@"A",此时@"A"的所有者为数组和firstObj。到第三行,@"A"的所有者为firstObj,未被回收,所以NSLog语句没有问题。最后退出if语句块时,stringArray和firstObj退出作用域,分别释放并回收数组和@"A"。

当然,你可以在定义stringArray和firstObj时指定__strong关键字:

    __strong NSMutableArray* stringArray = [NSMutableArray arrayWithObjects:@"A", @"B", @"C", nil];    __strong id firstObj = [stringArray objectAtIndex:0];

不过由于默认为强指针,所以__strong关键字是可以忽略的。

另外需要注意的是,由于强指针会一直保留着对象,所以当你确实需要将其释放时,需要手动将强指针赋值为nil,否则对象一直不会被回收,会导致系统内存资源不足。

再来说说弱指针。请看如下代码:

    NSString* strongPtr = [[NSString alloc] initWithCString:"A" encoding:NSUTF8StringEncoding];    __weak NSString* weakPtr = strongPtr;    NSLog(@"%@", weakPtr);    strongPtr = nil;    NSLog(@"%@", weakPtr);        double delayInSeconds = 3.0;    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){        NSLog(@"After 3 seconds: %@", weakPtr);    });

由于firstObj为弱指针,前两个NSLog输出为A,而最后一个则为(null)。这说明当对象被回收时,所有指向该对象的弱指针均会被置为nil,但这需要时间。

读到这里,你应该知道下面的代码有什么问题了吧:

    __weak NSArray* array = [[NSArray alloc] initWithObjects:@"Puzhi Li", nil];    NSLog(@"%@", array);

当启用了ARC之后,(基本上)不会出现内存泄漏、使用野指针、访问僵尸对象的情况,忘了那些让你咬牙切齿、不堪回首的记忆吧。这一切看上去是那么的自然,充分体现了和谐社会的优越啊 :)。


四、ARC中的类成员变量与属性

在定义类的成员变量时,同样可以使用__strong和__weak关键字来指定成员变量指针为强指针还是弱指针:

@interface TestViewController{    int i;    IBOutlet UIView *_aStrongView;    NSMutableArray *_aStrongArray;    __weak IBOutlet UIButton *_aWeakButton;}@property (nonatomic, strong) UIView *aStrongViewProperty;@property (nonatomic, weak) UIView *aWeakViewProperty;

注意,对象关联(IBOutlet)既可以关联到强指针(_aStrongView),也可以关联到弱指针(_aWeakButton)。如果界面对象或其父对象已经关联到强指针上,则默认情况下IB会将其关联到弱指针上。

当启用ARC后,定义属性时可以使用strong和weak附加特性来代替之前的retain和assign附加特性,分别表示属性对应的成员变量是强指针还是弱指针。ARC中的copy隐式包含了strong。


五、ARC中的类的方法

由于ARC会自动管理对象成员变量的保留和释放,所以大部分情况下根本不必重写类的dealloc方法。不过你依然可以重写dealloc方法,它会在对象被回收之前调用,但你不能在该方法中的成员变量上调用release,也不能调用[super dealloc]。


六、将现有代码转换为ARC

Xcode可以将未启用ARC的代码转换为支持ARC的代码。打开Xcode菜单中的Edit -> Refactor -> Convert to Objective-C ARC,按照提示操作即可。

另外,如果项目本身启用了ARC,但是你希望将未开启ARC的代码添加到项目中,而又不愿意将其修改为支持ARC,可以在项目TARGETS属性的Build Phases -> Compile Sources中双击这些.m文件,并在弹出窗口中指定-fno-objc-arc编译器指令即可。


七、使用ARC的其他注意事项
ARC确实是非常方便,苹果也推荐开发者都使用ARC,但是ARC并非万能,它只能自动处理Objective-C对象的保留和释放,当你使用C语言中的malloc()、free(),或者Core Foundation中的C函数时,ARC就无能为力了,你仍然需要自己去管理这部分的内存。
另外,当启用ARC后,编译器需要明确知道对象的生存期,因此某些之前明明正确的代码,在转换为ARC后就不再正确了,例如:
......switch (self.viewController.view.autoresizingMask){    case UIViewAutoresizingFlexibleLeftMargin://  {        NSString* s = @"Puzhi";        NSLog(@"%@", s);        break;//  }    case UIViewAutoresizingFlexibleRightMargin://  {        NSString* t = @"iOS";        NSLog(@"%@", t);        break;//  }    default:        break;}
上述语句不能通过编译,编译器会报出“Expected expression”或者“Switch case is in protected scope”等错误。原因是代码在case语句中定义了新的对象指针,其作用范围不明确。如果在case语句中定义新的对象指针,则必须将整个case语句块用大括号括住,这样新定义的对象指针s和t的作用范围就明确了。
在启用ARC后,你需要特别注意对象的生存期,否则程序可能不会按照你期待的那样执行,例如:
- (void)playSound{    ......    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];    audioPlayer.delegate = self;    [audioPlayer play];}
在未启用ARC时,程序可以正确地播放音频。但是当启用ARC后,却没有声音了。读了我前面所写文字之后,想必你可以猜出原因,以及如何去解决了。

原创粉丝点击