您的位置:首页 > 移动开发 > IOS开发

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

2013-08-05 15:48 204 查看
说起轿车,人们更青睐自动挡轿车,因为在开车过程中不用再去注意离合器和挂档,只需要控制油门和制动就可以了,这符合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后,却没有声音了。读了我前面所写文字之后,想必你可以猜出原因,以及如何去解决了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息