ARC简介

简单介绍引用计数

OC语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数器。如果想使某个对象继续存活的话, 那就递增其引用计数;用完了之后,就递减其引用计数。计数变为0,就表示没人关注此对象了,于是,就可以把它销毁。在对象生命期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别回递增及递减保留计数。

关于内存泄漏

这里“泄漏”的意思是:没有正确释放已经不再使用的内存。
即某对象无法被引用了却没有被释放,依旧有内存分配给它却无法访问,称之为内存泄漏。

以ARC简化引用计数

Clang编译器项目带有一个“静态分析器”,用于指明程序里引用计数出问题的地方。
举个例子

if ([self shouldLogMessage]) {
	NSString *message = [[NSString alloc] initWithFormat: @"I am object, %p", self];
	NSLog(@"message = %@", message);
}

此代码有内存泄漏问题,因为if语句块末尾并未释放message对象。由于在if之外无法引用message,所以此对象的内存泄漏了。
判断内存是否泄漏所用的规则很简明:调用NSString的alloc方法所返回的那个message对象的保留计数比期望值要多1。然而却没有与之对应的释放操作来抵消。因为这些规则很容易表述,所以计算机可以简单地将其套用在程序上,从而分析出有内存泄漏的对象,这就是“静态分析器”所要做的事情。
既然静态分析器可以查明内存管理问题,那么应该也可以根据需要预先加入适当的保留或释放操作以避免这些问题。自动引用计数的思路正式源于此。

自动引用计数

自动引用计数见名知义,就是自动管理引用计数,如果使用ARC,上列中的代码会自动改写为:

if ([self shouldLogMessage]) {
	NSString *message = [[NSString alloc] initWithFormat: @"I am object, %p", self];
	NSLog(@"message = %@", message);
	[message release];  //ARC自动添加的
}

使用ARC一定要记住,引用计数实际上还是要执行的,只不过保留与释放操作由ARC自动帮你添加了。
由于ARC会自动执行retain,release,autorelease,dealloc等操作,所以直接在ARC中调用这些内存管理方法是非法的,直接调用会产生编译错误。

使用ARC时必须遵循的方法命名规则

将内存管理语义在方法名中表示出来早已成为OC的惯例,而ARC将之确立为硬性规定。若方法名以下词语开头,则返回的对象归调用者所有:

  • alloc
  • new
  • copy
  • mutableCopy
    归调用者所有的意思是:调用上述四种方法的那段代码要负责释放方法返回的对象。
    若方法名不以上述四个词语开头则表示其返回的对象并不归调用者所有。在这种情况下,返回的对象会自动释放,所以其值在跨越方法调用边界后依然有效。要想使对象多存活一段时间,必须令调用者保留它才行。
    下列代码演示了ARC的用法:
+ (Person *)newPerson {
	Person *person = [[Person alloc] init];
	return person;
	//因为这个方法名以new开头并且以person结尾,返回时不需要对其进行多加操作
}

+ (Person *)somePerson {
	Person * person = [[Person alloc] init];
	return person;
	//因为这个方法名不以表示持有的方法名开头,ARC将在返回时添加autorelease
	//即 return [person autorelease];
}

- (void)doSomething {
	Person *personOne = [Person newPerson];
	Person *personTwo = [Person somePerson];
	//personOne被调用者持有,所以它需要释放
	//personTwo不被持有,所以它不需要释放
	//等效于添加[personOne release];
}

变量的内存管理语义

ARC也会处理局部变量和实例变量的内存管理。默认情况下,每个变量都是指向对象的强引用。对于某些代码来说,其语义和手动管理语义引用计数时不同,例如:

@interface cLass:NSObject {
	id _object;
}
@implementation Class
- (void)setup {
	_object = [OtherClass new];
}
@end

在手动管理引用计数时,实例变量_object并不会自动保留其值,而在ARC环境下则会自动保留。即:

- (void)setup {
	id tmp = [OtherClass new];
	_object = [tmp retain];
	[tmp release];
}

当然,在此情况下,retain和release可以消去。所以,ARC会将这两个操作简化掉,于是,实际执行的代码还是和原来一样。不过在编写setter方法时,使用ARC会更简单一些,如果不使用ARC,就需要这么写:

- (void)setObject:(id)object {
	[_object release];
	_object = [object retain];
}

但是这样写会出问题。如果两个值相同,当前对象还在引用这个值,那么设置方法中的释放操作会将这个值的保留计数降为0,从而导致系统将其回收,再执行保留操作就会令应用程序崩溃。使用ARC后就不会有这种疏漏了。在ARC环境下, 与刚才等效的设置函数可以这么写:

- (void)setObject:(id)object {
	_object = object;
}

ARC会用一种安全的方法来设置:先保留新值,再释放旧值,最后设置实例变量

ARC如何清理实例变量

刚才说过,ARC也负责对实例变量进行内存清理,要管理其内存,ARC就必须在“回收分配给对象的内存”时生成必要的清理代码。凡是具备强引用的变量,都必须释放,ARC会在dealloc中插入这些代码。即不使用ARC时,代码应该是这样:

- (void)dealloc {
	[_foo release];
	[_bar release];
	[super dealloc];
}

不过,如果有非OC的对象,比如CoreFoundation中的对象或是由malloc()分配在堆中的内存,那么仍然需要清理。然而不需要像原来调用超类的dealloc方法。在ARC下不能直接调用dealloc。ARC会自动在.cxx_destruct方法中生成代码并运行此方法。而在生成的代码中会自动调用超类的dealloc方法。ARC环境下, dealloc可以这么写:

- (void)dealloc {
	CFRelease(_CoreFoundationObject);
	free(_heapAllocatedMemoryBlob);
}

因为ARC会自动生成回收对象时所执行的代码,所以通常无需再编写dealloc方法。这样能减少项目源代码的大小,而且可以省去其中一些样板代码。

参考文献

《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》

猜你喜欢

转载自blog.csdn.net/streamery/article/details/105258532
arc