小蓝书学习(五)
第29条:理解引用计数
Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数器,如果想使某个对象继续存活,就给他递增引用计数;用完了之后,就递减其计数,直至其变为0。
引用计数工作原理
在引用计数架构下,对象有个计数器,用以表示当前有多少个事物想令其对象继续存活下去。这在Objective-C中叫做“保留计数”(也叫“引用计数”)。NSObject协议中声明了下面三个方法用于操作计数器,以递增或递减其值:
Retain递增保留计数release递减保留计数autorelease待稍后清理“自动释放池”时,再递减保留计数。
对象创建出来时,其计数保留为1。若想令其继续存活,则需使用retain方法。要是某部分代码不在使用此对象,那就要调用release方法或者autorelease方法。最终当保留计数归零时,对象就要回收了,也就是说,系统会将其占用的内存 标记为“可重用”。此时,所有只想该对象的引用也就变得无效了。这里笔者放两张图演示其过程:


在图5-2中,刚开始
ObjectB和ObjectC都引用了ObjectA,而后当两者都不使用其之后,便可以回收了
NSMutableArray* array = [[NSMutableArray alloc] init];
NSNumber* number = [[NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
NSLog("%@", number);
在上述例子中,直接调用了release方法,在ARC下无法编译。由于调用alloc方法所返回的对象有调用者所拥有。也就是说,调用者已经表达了令该对象继续存活下去的意愿。这样的话,我们显然知道number对象在调用release方法后可以继续存活,但是基于某些原因,number对象所占内存也许会被回收,所以我们应该调用完release方法之后清空指针number = nil这样可以有效避免悬挂指针的产生。
属性存取方法中的内存管理
属性存取方法也存在内存管理模式,对象也可以保留别的对象,这一般通过访问属性来实现。若属性为“strong关系”,则设置的属性值会保留。举例说明:
- (void)setFoo:(id)foo {
[foo retain];//保留新值
[_foo release];//释放旧值
_foo = foo;//更新实例变量
}
此方法将保留心智并释放旧值,然后更新实例变量,令其指向新值。顺序不能改变,否则对象可能在release之后被销毁,产生悬挂指针。
自动释放池
在OC的引用计数架构中,自动释放池是一个重要的特性。调用release会立刻递减对象的保留计数,然而有时候可以不调用它,改为调用autorelease,此方法会在稍后递减计数,通常是在下一次“实践循环”时递减,不过也可能执行得更早些。
- (NSString *)stringValue {
NSString *str = [[NSString alloc] initWithFormat:@"I am this:%@", self];
return str;
}
在上述方法中,返回的str对象其保留计数比期望值要多1,因为调用alloc会令保留计数加1,而又没有与之对应的释放操作。这样做的话,会使调用者要负责处理多一次的保留操作。这里我们可以用到autorelease。他会在稍后释放对象,从而给调用者留下了足够长的时间,使其可以在需要时先保留返回值。这样做,我们就需要将最后一行代码改为return [str autorelease]。
要点
- 引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活。当保留计数降为0时,对象就被销毁了。
- 在对象生命期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。
第30条:以ARC简化引用计数
使用ARC时一定要记住,引用计数实际上还是执行的,只不过保留与释放操作现在是由ARC自动为你添加。稍后才会看到,除了为方法所返回的对象正确运用内存管理语义之外,ARC 还有更多的功能。不过,ARC 的那些功能都是基于核心的内存管理语义而构建的,这套标准语义贯穿于整个Objective-C语言。
由于ARC会自动执行retain、release、autorelease等操作,所以直接在ARC下调用这些 内存管理方法是非法的。具体来说,不能调用下列方法:
retainreleaseautoreleasedealloc
实际上,ARC 在调用这些方法时,并不通过普通的Objective-C 消息派发机制,而是直 接调用其底层C 语言版本。这样做性能更好,因为保留及释放操作需要频繁执行,所以直 接 调 用 底 层 丽 数 能 节省很多CPU周期。比方说,ARC会调用与retain等价的底层函数 objc_retain。这也是不能覆写retain、release 或autorelease 的缘由,因为这些方法从来不会被直接 调用。笔者在本节后面的文字中将用等价的Objective-C 方法来指代与之相关的底层C语言 版本,这对 于那些 手动管理过引用计数的开发者来说更易理解。
使用ARC时必须遵守的方法命名规则
若方法名以下列词语开头,则其返回的对象归调用者所有:
allocnewcopymutableCopy
意思就是调用这四种方法的那段代码要负责释放方法所返回的对象。也就是说,这些对象的保留计数是正值,而调用了这四种方法的那段代码要将其中一次保留操作抵消。若方法名不以上述四个词语开头,则表示其所返回的对象并不归调用者所有。这种情况下,返回的对象会自动释放,也就是使用了autorelease。
ARC除了自动调用“保留”和“释放”方法外,其也可以优化操作,比如两个在一起的保留和释放,它就会将这一对直接移除,不执行这两个代码。
在ARC环境下编译代码时,必须考虑“向后兼容性”,以兼容那些不使用ARC的代码,其实ARC的简化操作是因为其调用的特殊函数,它会把autorelease方法改为调用objc_autoreleaseReturnValue函数,把retain方法改为objc_retainAutoreleaseReturnValue函数。
变量的内存管理语义
ARC也会处理局部变量和实例变量的内存管理,默认情况喜爱每个变量都是指代对象的强引用,一定要理解的事实例变量的真正意思是什么。对于某些代码来说,语义和手动内存管理存在一定差异。例如在编写setter方法的时候,不用ARC模式是这样写:
- (void)setObject:(id)object {
[_object release];
_object = [object retain];
}
这样写的问题是新值和实例变量的值是一样的,只有当前对象还在用引用这个值的时候,那么设置方法中的释放操作会令值保留计数变为0,那么就会被系统回收从而产生crash,接下来retain操作则是错误的,使用ARC的时候不会存在这种流失的现象
- (void)setObject:(id)object {
_object = object;
}
ARC会用一种安全的方式来设置,先保留新值,再释放旧值,最后设置实例变量
ARC如何清理实例变量
要管理其内存,ARC就必须在“回收分配给对象的内存”是生产必要的清理代码。ARC环境下,dealloc方法可以这样来写:
- (void)dealloc {
CFRelease ( _coreFoundationObject);
free ( _heapAllocatedMemoryBlob);
}
因为ARC会自动生成回收对象时所执行的代码,所以通常无需再编写dealloc方法。这能减少项目源代码的大小,而且可以省去其中一些样板代码。
要点:
- 有ARC之后,程序员就无须担心内存管理问题了。使用ARC来编程,可省去类中的许多“样板代码”。
- ARC管理对象生命期的办法基本上就是:在合适的地方插入“保留”及“释放”操作。在ARC环境下,变量的内存管理语义可以通过修饰符指明,而原来则需要手工执行“保留”及“释放”操作。
- 由方法所返回的对象,其内存管理语义总是通过方法名来体现。ARC将此确定为开发者必须遵守的规则。
- ARC只负责管理OC对象的内存。尤其要注意
CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease。
第31条:在delloc方法中只释放引用并解决监听
对象在经历其生命期后,最终会被系统回收,就会执行delloc方法。在每个对象的生命期中,只会执行一次(在保留计数降为0的时候),但无法保证何时会执行。
在delloc方法中我们应该释放对象所拥有的引用,ARC会通过自动生成的cxx_destruct方法,在delloc中为你自动添加这些释放代码。对象所拥有的其他对象也要释放。在delloc方法中,还需要把原来配置过的观测行为都清理掉。
delloc方法可以这么来写:
- (void)dealloc {
CFRelease(coreFoundationObject);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
如果是手动调用而不是ARC,记得最后要加上
[super delloc]
要点:
- 在
dealloc方法里,应该做的事情就是释放指向其他对象的引用,并取消原来订阅的“键值观测”(KVO)或NSNotificationCenter等通知,不要做其他事情。 - 如果对象持有文件描述符等系统资源,那么应该专门编写一个方法来释放此种资源这样的类要和其使用者约定:用完资源后必须调用
close方法。 - 执行异步任务的方法不应在
dealloc里调用;只能在正常状态下执行的那些方法也不应在dealloc里调用,因为此时对象已处于正在回收的状态了。
第32条:编写“异常安全代码”时留意内存管理问题
要点:
- 捕获异常时,一定要注意将try块内所创立的对象清理干净
- 在默认情况下,ARC不生成安全处理异常所需的清理代码。开启编译器标志后,可生成这种代码,不过会导致应用程序变大,而且会降低运行效率
第33条:以弱引用避免保留环
对象中经常会有一种情况,就是几个对象都以某种方式互相引用,从而形成“环”。这种情况通常会泄漏内存。因为最后没有别的东西会引用环内的对象。就没有办法为外界所访问了,但是对象之间尚有引用,这会让它们都能存活下去,而不会被系统所回收。
避免保留环最佳方式就是弱引用。这种引用经常用来表示“非拥有关系”。将属性声明为unsafe_unretained就可以了。举例如下:

这里通过一张图演示unsafe_unretained与weak的区别:

要点:
- 将某些引用设为
weak,可以避免出现“保留环” weak引用可以自动清空,也可以不自动清空。自动清空是随着ARC而引入的新特性,有运行期系统来实现。在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。
第34条:以“自动释放池块”降低内存峰值
OC对象的生命中取决于其引用计数,在OC的引用计数架构中,有一个特性叫做“自动释放池”。释放对象有两种方式:一种是使用 release方法,使其保留计数立即递减;另一种是调用autorelease方法。当我们清空自动释放池时,系统会想其中的对象发送release消息。
创建自动释放池语法如下:
@autoreleasepool{
//...
}
下面我通过一段代码来解释自动释放池使用的实际作用:

在这段代码中 ,会创建出一些临时对象,记录很多条时,内存中就会多出许多不必要的临时对象,这时我们使用自动释放池就可以解决这个问题。

加上这个自动释放池之后,应用程序在执行循环时的内存峰值就会降低,不再像原来那么高了。内存峰值(high-memory waterline)是指应用程序在某个特定时段内的最大内存用量(highest memory footprint)。新增的自动释放池块可以减少这个峰值,因为系统会在块的末尾把某些对象回收掉。而刚才提到的那种临时对象,就在回收之列。
自动释放池机制就像"栈"(stack)一样。系统创建好自动释放池之后,就将其推入栈中,而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。
要点:
- 自动释放池排布在栈中,对象收到
autorelease消息后,系统将其放入最顶端的池里。 - 合理运用自动释放池,可降低应用程序的内存峰值。
@autoreleasepool这种新式写法能创建出更为轻便的自动释放池。
第35条:用“僵尸对象”调试内存管理问题
Cocoa提供了“僵尸对象”这一功能,当功能启用后,系统会把所有已经回收的实例转化为“僵尸对象”,而不会真正回收它们。这种对象内存不可能遭到覆写,并且收到消息后会抛出异常,准确说明发送过来的消息,并描述回收之前的那个对象。僵尸对象是调试内存管理问题的最佳方式。
将NSZombieEnabled环境变量设为YES,即可开启此功能。开启功能后,系统会修改对象的isa指针,指向特殊的僵尸类,从而使对象变为僵尸对象,他会相应所有选择子,响应方式为:打印一条包含信息内容及其接收者的消息,然后终止应用程序。
要点:
- 系统在回收对象时,可以不将其真的回收,而是把它转化为僵尸对象。通过环境变量
NSZombieEnabled可开启此功能。 - 系统会修改对象的 isa 指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能够响应所有的选择子,响应方式为∶打印一条包含消息内容及其接收者的消息,然后终止应用程序。
第36条:不要使用retainCount
ARC模式已经被弃用这个方法,原因是它返回的保留计数只是某个对象在某个具体时间的引用计数,完全没有特殊的参考意义,现在不仅存在自动释放池,还有ARC模式的自动管理引用计数,那么etainCOunt完全不能反映真实的引用计数。
要点:
- 对象的保留计数看似有用,实则不然,因为任何给定时间点上的“绝对保留计数”都无法反映对象生命期的全貌。
- 引入ARC之后,
retainCount方法就正式废止了,在ARC下调用该方法会导致编译器报错。
2696

被折叠的 条评论
为什么被折叠?



