《编写高质量iOS与OS X代码的52个有效方法》--第五章 第29条
(ps:此乃读书笔记,加深记忆,仅供大家参考)
第5章 内存管理
ARC几乎把所有内存管理事宜都交给编译器决定,开发者只需专注于业务逻辑。
第29条:理解引用计数
Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数器。如果想使某个对象继续存活,那就递增其引用计数;用完了之后,就递减其引用计数。计数变为0,就表示没人关注此对象了,于是,就可以把它销毁。
引用计数工作原理
在引用计数架构下,对象有个计数器,用以表示当前有多少个事物想令此对象继续存活下去。这在Objective-C中叫做“保留计数”(retain count),不过也可以叫“引用计数”(reference count)。NSObject协议声明了下面三个方法用于操作计数器,以递增或递减其值:
- Retain 递增保留计数。
- release 递减保留计数。
- autorelease 待稍后清理“自动释放池”(autorelease pool)时,再递减保留计数。
查看保留计数的方法叫做retainCount, 此方法不太有用,即便在调试时也如此,所以笔者(苹果公司)并不推荐大家使用这个方法。
对象创建出来时,其保留计数至少为1。 最终当保留计数归零时,对象就回收了(deallocated),也就是说,系统会将其占用的内存标记为“可重用”(reuse)。此时,所有指向该对象的引用也都变得无效了。
对象如果持有指向其他对象的强引用(strong reference),那么前者就“拥有”(own)后者。也就是说,对象想令其所引用的那些对象继续存活,就可将其“保留”。等用完了之后,再释放。
如果按“引用树”回溯,那么最终会发现一个“跟对象”(root object)。在Mac OS X应用程序中,此对象就是NSApplication对象;而在iOS应用程序中,则是UIApplication对象。两者都是应用程序启动时创建的单例。
NSMutableArray * array = [[NSMutableArray alloc] init];
NSNumber * number = [[NSNumber alloc] initWithInt:2333];
[array addObject:number];
[number release];
//do something with 'array'
[array release];
在Objective-C中,调用alloc方法所返回的对象由调用者所拥有。也就是说,调用者已通过alloc方法表达了想令该对象继续存活下去的意思。不过请注意,这并不是说此对象此时的保留计数必定是1。在alloc或“initWitInt:”方法的实现代码中,也许还有其他对象也保留了此对象,所以,其保留计数至少为1。不能说保留计数一定是某个值,只能说你所执行的操作是递增了该计数还是递减了该计数。
number对象调用release释放之后,仍然存活.因为数组还在引用着它。然而绝不应该假设此对象一定存活,也就是说,不要像下面这样编写代码:
NSNumber * number = [[NSNumber alloc] initWithInt:2333];
[array addObject:number];
[number release];
NSLog(@"number = %@", number);
如果调用release由于某些原因,其保留计数降至0,那么number对象所占内存也许会回收,这样的话,在调用NSLog可能就将是程序崩溃了。这里说“可能”,而没说“一定”,因为对象所占的内存在“解除分配”(deallocated)之后,只是放回“可用内存池”(avaiable pool)。如果执行NSLog时尚未复写对象内存,那么该对象仍然有效,这时程序不会崩溃。
为避免在不经意间使用了无效对象,一般调用完release之后都会清空指针。这就能保证不会出现可能指向无效对象的指针,这种指针通常称为“悬挂指针”(dangling pointer)。
NSNumber * number = [[NSNumber alloc] initWithInt:2333];
[array addObject:number];
[number release];
number = nil;
属性存取方法中的内存管理
若属性为“strong关系”(strong relationship),则设置的属性值会保留。
- (void)setFoo:(id)foo
{
[foo retain];
[_foo release];
_foo = foo;
}
此方法将保留新值并释放旧值,然后更新实例变量,令其指向新值。顺序很重要。
自动释放池
调用release会立刻递减对象的保留计数(而且还有可能令系统回收此对象),然而有时候可以不调用它,改为调用autorelease,此方法会在稍后递减计数,通常是在下一次“时间循环”(event loop)时递减,不过也可能执行的更早些。
此特性很有用,尤其是在方法中返回对象时更应该使用它。在这种情况下,我们并不总是想令方法调用者手工保留其值。
- (NSString *)stringValue
{
NSString * str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return self; //return [str autorelease];
}
此时返回的str对象其保留计数比期望值要多1(+1 retain count),因为调用alloc会令保留计数加1,而又没有与之对应的释放操作。
这里应该用autorelease,它会在稍后释放对象,从而给调用者留下了足够长的时间,使其可以在需要时先保留返回值。换句话说,此方法可以保证对象在跨越“方法调用边界”(method call boundary)后一定存活。
保留环
通常采用“弱引用”(weak reference)来解决此问题,或是从外界命令循环中的某个对象不再保留另一个对象。这两种方法都能打破保留环,从而避免内存泄露。
要点
- 引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活。当保留计数降为0时,对象就被销毁了。
- 在对象生命期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增或递减保留计数。
网友评论