9.1 对象生命周期
说明:4个过程
- 诞生:通过
alloc
或new
方法实现- 生存:接收消息并执行操作
- 交友:通过复合以及向方法传递参数
- 死去:被释放掉
9.1.1 引用计数
说明:也叫
保留计数
,每个对象都有一个与之想关联的整数,被称作它的引用计数器
或保留计数器
。
相关操作 | 说明 | 相关方法 |
---|---|---|
引用计数器 初始化 |
被设置为1 | alloc 、new 、copy |
引用计数器 +1 |
新增对象引用时 | retain |
引用计数器 -1 |
引用生命周期结束或引用被断开时 | release |
销毁对象 | 释放掉已经分配的全部相关资源 | dealloc |
retain实例方法
说明:增加对象的
引用计数器
的值。
注意:Objective-C
会在需要的时候自动调用它。
原型:id
1 | /** |
release实例方法
说明:减少对象的
引用计数器
的值。
注意:Objective-C
会在需要的时候自动调用它。
技巧:因为retain
方法返回一个id类型的值,可以在接收其他消息的同时进行retain
调用,增加对象的引用计数器
的值并执行其他操作。
原型:id
1 | - (oneway void) release; |
retainCount实例方法
说明:获取对象的
引用计数器
的值。
原型:id
1 | /** |
1 |
|
9.1.2 对象所有权
说明:如果一个对象(或函数)内有指向其他对象的实例变量,则称该对象(或函数)拥有这些对象,并负责确保对其拥有的对象进行清理。
9.1.3 访问方法中的保留和释放
说明:
setEngine
方法的第一个内存管理版本。
1 |
|
9.1.4 自动释放
说明:有些情况下,拥有对象的实体并不能负责清理拥有的对象,如下
1 | // 不能在description中释放对象,因为先释放decription字符串对象再返回它,则保留计数器的值归0,对象马上被销毁。 |
解决办法:不够优雅
1 | // 将返回的字符串赋在某个变量中 |
9.1.5 所有对象放入池中
关键字:
@autoreleasepool
、NSAutoreleasePool
说明:自动释放池
是一个用来存放对象的池子(集合),并且能够自动释放。当自动释放池被销毁时,会想该池中所有的对象发送release
消息。
autorelease实例方法
说明:当向一个对象发送
autorelease
消息时,实际上是将该对象添加到自动释放池中。
原型:id
1 | /** |
1 | - (NSString *)description { |
1 | // NSLog函数的代码运行结束以后,自动释放池会被自动销毁(假设上下文存在已经创建好的自动释放池) |
9.1.6 自动释放池的创建和销毁
说明:自动释放池应该什么时候创建?什么时候销毁?
9.1.6.1 创建
说明:有两种方式
自动释放池的创建 | 说明 | 备注 |
---|---|---|
@autorelease{} |
{} 内的代码都会被放入这个新池子中 |
定义在{} 内的变量在外部无法使用 |
NSAutoreleasePool 对象 |
创建和释放NSAutoreleasePool 对象之间的代码会使用这个新的池子 |
性能不如@autorelease{} 方式 |
扩展:可以使用
drain方法
清空自定释放池中的对象而不销毁自动释放池(Mac OS 10.4+)
9.1.6.2 销毁
说明:使用
AppKit
时,Cocoa
定期自动地为你创建和销毁自动释放池,通常是在程序处理完当前事件(如鼠标单击或者键盘按下)以后执行这些操作。
9.1.7 自动释放池的工作流程
1 |
|
9.2 Cocoa 的内存管理规则
说明:
Cocoa
有一些内存管理约定,它们都是一些很简单的规则,可用用于整个工具集內。
- 如果使用
new
、alloc
、copy
方法创建了一个对象:当不再使用该对象时,应该向该对象发送一条release
或autorelease
消息- 如果通过其他方法获得一个对象:如果对象的保留计数器的值为1,而且已经被设置为自动释放,那么你不需要执任何操作来确保对象的到清理,除非打算在一段时间内拥有该对象,则需要
保留
它并确保在操作完成时释放
它。- 如果
保留
了某个对象:需要(最终)释放
或自动释放
该对象(即保持retain
方法和release
方法使用次数相等)。
技巧:以上规则可以归结为下表(两个维度:对象的来历?用完就销毁还是保留?)
对象的来历 | 用完就销毁 | 和拥有者同命 |
---|---|---|
alloc 、new 、copy |
不在使用时释放对象 | 在dealloc方法中释放对象 |
其它方法 | 不需要执行任何操作 | 获得对象时保留 ,在dealloc 方法中释放 对象 |
9.2.1 临时对象
说明:临时对象指的是,在代码中使用某个对象,但是并未打算长期拥有该对象。
内存管理:如果是用new
、alloc
、copy
方法获得这个对象,就需要安排好该对象的内存释放(通常使用release
)。
alloc
1 | // 维度1:通过alloc创建可变数组 |
其它方法
1 | // 维度1:其它方法(工厂方法arrayWithCapacity已经将该对象的引用计数器设置为1且设置了自动释放,即已经放在了自动释放池中) |
其它方法(全局单例)
1 | NSColor *color; |
9.2.2 拥有对象
说明:
拥有对象
指的是希望在多个代码中一直拥有某个对象,比如
- 将对象放进
集合
中(NSArray
、NSDictionary
等)- 作为其它对象的
实例变量
使用- 作为
全局变量
使用(比较罕见)内存管理:见9.2-技巧
- 使用
new
、alloc
、copy
方法获得一个对象:只需保证在拥有者的dealloc
方法中释放它- 其它方法获得一个对象:获得后
保留
该对象,并保证在拥有者的dealloc
方法中释放它new
、实例变量
1 | - (void) doStuff { |
其它方法、实例变量
1 | - (void) doStuff { |
清理自动释放池
说明:自动释放池被清理的时间是完全确定的
- 在代码中手动销毁
- 使用AppKit时是在循环结束时销毁
原理:自动释放池存放在栈中,新建的自动释放池被添加到栈顶,接收
autorelease
消息的对象将被放入最顶端的自动释放池。
注意:自动释放池的分配和销毁操作代价很小,如果一个循环中会创建大量对象,可以创建在循环中常见自己的自动释放池,每创建一批就释放一批。
1 | // 创建自动释放池 |
9.2.3 垃圾回收
说明:
Objective-C2.0
引入了自动内存管理机制,也称为垃圾回收
触发:类似自动释放池
- 在时间循环结束时触发
- 也可以自己触发(如果不是GUI程序)
开启:
垃圾回收
是一个可选择的是否启动的功能(项目信息窗口->Build Settings选项卡->Require[-fobjc-gc-only]选项)
限制:只支持OS X
应用开发,无法在iOS
应用程序上应用。
扩展:苹果对iOS
开发的一些建议
- 不要在自己的代码中使用
autorelease
方法- 不要使用会返回自动释放对象的一些便利方法(比如
NSString
中以stringWith
开头的工厂方法)
9.2.4 自动引用计数
背景:
iOS
不支持垃圾回收,因为移动设备比电脑更加私人化、资源更少,垃圾回收存在潜在的体验问题。但苹果提供了另外一个方案来祢补,那就是自动引用计数(automatic regerence counting, ARC)
说明:ARC
不是垃圾回收器,它是在编译期(而不是运行期)工作的,它在代码中插入了合适的retain
和release
语句。
注意:ARC
是一个可选的功能,必需明确地启用或禁用。
开发环境限制:以下是编写(或运行)ARC
代码所需的条件
- Xcode4.2 以上的版本
- Apple LLVM 3.0 以上版本的编译器
OS X 10.7
以上版本的系统运行环境限制:以下是运行移动设备必需满足的条件
- ios 4.0以上的移动设备或
OS X10.6
以上版本的64位系统的电脑归零弱引用
需要iOS 5.0
或OS X 10.7
以上版本的系统作用对象限制:
ARC
只对可保留的对象指针(ROPs)
有效
- 代码块指针
Objective-C
对象指针- 通过
_attribute((NSObject))
类型定义的指针技巧:如果想在代码中使用
ARC
,必需满足以下三个条件
- 能够确定哪些对象需要进行内存管理
- 能够表明如何去管理对象:就是说必需能够对某个对象的引用计数器的值进行加1和减1的操作(
NSObject
的子类都可以)- 有可行的办法传递对象的所有权(在调用者和接受者之间)
注意:如果使用的指针不支持
ARC
,那么你不得不亲自手动管理它们。
弱引用
说明:通常变量和
Objective-C
对象之间都是强引用
,可以通过对属性使用了assign
特性声明为弱引用
。
用途:处理保留循环(retain cycle)
带来的内存泄漏。
限制:弱引用
指向的对象有可能被提前释放,直接使用会导致问题。
归零弱引用
说明:在指向的对象被释放之后,这种弱引用就会被设置为
nil
,然后就可以像平常的指针一样被处理了。
语法:
方式 | 说明 | 备注 |
---|---|---|
_weak 关键字 |
声明变量时使用_weak 修饰 |
_weak NSString *myString; |
@property(weak) |
对属性使用weak 特性 |
@property(weak) NSString *myString; |
_unsafe_unretained 关键字和unsafe_unretained 特性 |
告诉ARC 这个特殊的引用是弱引用 |
前提是:不支持弱引用、使用了ARC |
兼容性:
iOS 5+
和OS X 10.7+
属性命名限制:使用ARC
的时候,有两种命名规则
- 属性名称不能以
new
开头- 属性不能只有一个
read-only
而没有内存管理特性(除非启用了ARC
功能)注意:内存管理的
关键字
和特性
是不能一起使用的,两者相互排斥。
扩展:强引用
也有自己的_strong
关键字和strong
特性
将已有的项目转换成支持ARC
的
前提:必须确保垃圾回收机制没有启动(
垃圾回收
和ARC
是无法一同使用的)。
说明:ARC
默认是启动了的,如果一个项目没有是在没有启动ARC
的情况下开发的,可以转换(通过Edit->Convert->To Objective-C ARC…)
桥接转换
指针分两类:
ROP
和non-ROP
分类 | 说明 | 是否被ARC (启用了的话)管理 |
---|---|---|
可保留对象指针(ROP ) |
NSobject的所有子类 | 是 |
不可保留对象指针(non-ROP ) |
C语言集合类型 | 否 |
拥有者权限:这个概念只有在启用了
ARC
的情况下才有意义。指的是ROP
和non-ROP
相互转换时指针所有权情况,用来告诉ARC
如何工作用途:通过
桥接转换
,可以在转换类型的同时控制拥有者权限
说明:有3种类型的桥接转换
关键字 | 语法 | 对象的保留计数器 | 备注 |
---|---|---|---|
_bridge |
non-ROP变量=(__bridge)ROP变量; 或ROP变量=(__bridge)non-ROP变量; |
不变化 | 指针的所有权仍会留在原变量 |
_bridge_restained |
non-ROP变量=(__bridge_restained)ROP变量; |
加1 | |
_bridge_transfer |
ROP变量=(__bridge_transfer)non-ROP变量; |
减1 |
技巧:
- 结构体(
struct
)和集合体(union
)不能使用ROP
作为成员。可以通过使用void *
和桥接转换
来解决这个问题- 有时需要释放不支持
ARC
的对象或执行其他清理操作,所以仍要实现dealloc
方法,但是不能直接调用[super dealloc]
注意:
ARC
中的代码存在如下限制
内存管理方法 | 不能调用 | 不能重写 |
---|---|---|
retain | - [x] | - [x] |
retainCount | - [x] | - [x] |
release | - [x] | - [x] |
autorelease | - [x] | - [x] |
dealloc | - [x] |
1 | // ROP |
9.3 异常
说明:异常就是异常事件,比如数组溢出,如果
捕捉
并处理
,就会痰乱程序流程。
- 异常对象:
Cocoa
中使用NSException
类来表示异常,可以创建NSException
子类作为自己的异常(如果通过其它类型的对象来抛出异常
,Cocoa
不会处理它们)- 抛出异常:在运行时系统中创建并处理异常的行为
- 捕捉异常:处理被
抛出
的异常的行为注意:
- 如果要项目支持异常特性,要确保
-fobj-exceptions
(Enable Objecytive-C Exception)项被打开Cocoa
框架处理错误的方式通常是退出程序- 如果一个异常被抛出但没有被捕捉,程序会在异常断点处停止运行并通知有这个异常
9.3.1 与异常有关的关键字
说明:都以
@
开头
关键字 | 作用 |
---|---|
@try |
定义可能包含异常的代码块 |
@catch |
定义处理已抛出异常的代码块,接收一个参数,通常是NSException 或其子类 |
@finally |
定义无论是够有抛出异常都会执行的代码块 |
@throw |
抛出异常 |
语法:
@try-catch-finally
1 | @try { |
9.3.2 捕捉不同类型的异常
说明:可以根据需要处理的异常类型过使用多个
@catch
代码块。处理代码应该按照从具体到抽象的顺序排序,并在最后使用一个通用的处理代码
1 | @try { |
注意:C语言程序员经常会在异常处理代码中使用
setjmp
和longjmp
语句。在@try
中则不可以,但可以使用goto
和return
语句退出异常处理代码。
9.3.3 抛出异常
说明:异常的抛出分两种,
自动
和手动
,后者有2
中方式
手动抛出异常 | 异常对象类型 |
---|---|
@throw 异常对象 |
id |
向某个异常对象发送raise 消息 |
NSException 或其子类 |
注意:在
@try
和@catch
中都可以抛出异常,后者会引发下一个异常处理调用(@finally
会在@throw
之前被调用)
扩展:Objective-C
的异常机制与C++
的异常机制兼容。
性能问题:@try
建立异常不会产生消耗,但捕捉异常会消耗大量资源并影响程序运行的速度。
1 | NSExceptionn *theException = [NSException exceptionWithName: _]; |
1 | @try { |
9.3.4 异常也需要内存管理
说明:如果代码出现了异常,程序会被中断,原本没有内存问题的代码或许会因此出现内存泄漏。
问题描述
1 | - (void)mySimpleMethod { |
解决方式:@try-@finally
1 | - (void)mySimpleMethod { |
9.3.5 异常和自动释放池
说明:通常,开发人员并不知道异常对象何时释放,所以异常几乎总是作为自动释放对象创建。
注意:@finally
代码块会在@catch
中的@throw
语句执行之前被调用,因此如果在@finally
将自动释放池销毁,那么就会导致僵尸异常
。
僵尸异常示例
1 | - (void)myMechod { |
避免僵尸异常:在自动释放池
外保留
异常对象
1 | - (void)myMechod { |