引用计数
引用计数表
每个对象都有一个对应的引用计数表,苹果使用引用计数表来管理各对象的引用计数,表中记录有各对象内存块的地址,从而根据地址找到各对象的内存块。
这样做的话有几个好处:
- 对象的内存块的分配无需考虑内存块的头部(如果没有引用计数表,则每个对象的内存块头部将用来存放引用计数)
- 即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被破坏,就能够确认各内存块的位置。
- 利用工具检测内存泄漏时,引用计数表的记录也有助于检测各对象的持有者是否存在。
生成一个对象或者持有一个对象都会使引用计数加1,释放对象时引用计数减1,当引用计数为0时,对象被废弃。
MRC规则
内存管理的思考方式
自己生成的对象自己持有
比如使用alloc、new、copy、mutableCopy等方法,引用计数自动加1
|
|
非自己生成的对象,自己也能持有
除了上述方法之外生成的对象,并且使用retain方法持有对象。
|
|
不再需要自己持有的对象时释放
如:release方法
非自己持有的对象无法释放
如:dealloc方法
retain、retainCount、release的实现
调用同一个函数__CFDoExternRefOperation,根据对象的内存地址和引用计数表去修改对象的引用计数
autorelease
对象调用autorelease后被加入NSAutoreleasePool自动释放池中,当自动释放池废弃时,对象会自动调用release方法
在大量产生autorelease的对象时,有可能会出现内存不足的情况,这时应该手动生成一个NSAutoreleasePool,把这些对象放进这个池子里,然后用完之后再手动[pool drain]释放池子
autorelease的实现
调用autorelease方法实际上是通过add方法将对象放入一个NSAutoreleasePool自动释放池中。先是用push方法获得一个NSAutorelease自动释放池,然后用pop方法废弃一个NSAutorelease自动释放池
|
|
autorelease一个NSAutoreleasePool对象会发生异常
因为无论调用哪个对象的autorelease方法,实现上都是调用NSObject类的autorelease实例方法,但是对于NSAutorelease类,autorelease实例方法已被该类重载。因此运行时就会出错
ARC规则
ARC只是自动的帮助我们处理引用计数的相关部分
ARC自动追加所有权修饰符,附有这些修饰符的自动变量初始化为nil
id strong obj0等价于id strong obj0 = nil
__strong
所有对象默认的所有权修饰符,表示对对象的强引用,持有强引用的变量超出其作用域时,随着强引用的失效,引用对象随之释放
|
|
__strong变量互相赋值也能正确管理对象内存
|
|
__weak
__weak修饰符表示弱引用,不能持有对象实例,解决__strong循环引用的问题
|
|
有__weak修饰某对象的弱引用时,如果对象被废弃,该弱引用将失效自动赋值为nil
在访问附有__weak修饰符的变量时必须访问注册到自动释放池中的对象,因为__weak修饰符只持有对象的弱引用,在访问引用对象的过程中,该对象有可能被废弃,如果把要访问的对象注册到自动释放池中,则在自动释放池废弃前对象都存在。
__unsafe_unretained
|
|
同__weak一样,不能持有对象,生成的对象会立即释放
与__weak不同,当对象被废弃时,并不指向nil而是产生一个野指针。
__autoreleasing
在ARC中用autoreleasing修饰的对象等价于MRC中对象调用autorelease方法
同__strong一样,没有显式指定__autoreleasing的对象,也会自动注册到autoreleasePool
ARC规则
- 不能使用
retain/releaase/retainCount/autorelease - 不能使用
NSAllocateObject/NSDeallocateObject - 不要显示调用
dealloc - 使用
@autoreleasepool块代替NSAutoreleasePool - 不能使用区域
NSZone - 对象型变量不能作为C语言结构体的成员
- 显示转换
“id”和“voic *”(通过__bridge转换,id和void *就能互相转换)
|
|
无论是ARC无效还是有效用于对象生成持有的方法必须遵守以下命名规则:alloc、new、copy、mutableCopy
但是在ARC有效时新增init命名规则,该方法必须是实例方法,并且必须要返回对象。返回的对象类型应为id类型或者该方法声明类的对象类型,或者是该类的超类或者子类。返回的对象并不注册到autoreleasepool上,基本上只是对alloc方法返回值的对象进行初始化并返回该对象
属性
| 声明的属性 | 对应的所有权修饰符 |
|---|---|
| assign | __unsafe_unretained修饰符 |
| copy | __strong修饰符 |
| retain | __strong修饰符 |
| strong | __strong修饰符 |
| unsafe_unretained | __unsafe_unretained修饰符 |
| weak | __weak修饰符 |
copy属性不是简单的赋值,它赋值的是通过NSCopying接口的copyWithZone:方法复制赋值源所生成的对象
数组
使用calloc函数可以使分配给附有__strong修饰符变量的数组初始化为nil,比如:
|
|
如果使用C函数的malloc则分配后还需要用memset函数将内存填充为0。
比如这样写是不对的:
|
|
这里使用malloc函数分配的内存区域没有被初始化为0,因此nil会被赋值给附有__strong修饰符的并被赋值了随机地址的变量中,从而释放一个不存在的对象。
动态数组中操作附有__strong修饰符的变量时需要手动释放内存。如:free(array);因为动态数组编译器不能确定数组的生存周期,无从处理。
而静态数组则不需要,因为在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码。
ARC实现
__strong修饰符
alloc、new、copy、mutableCopy生成并持有的对象内部实现如下:
|
|
其他生成对象如:id __strong obj = [NSMutableArray array];的内部实现如下:
|
|
__weak修饰符
|
|
若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象:
|
|
每使用一次附有__weak修饰符都会注册一次对象到自动释放池,例如:
|
|
这里注册了3次自动释放池。但是将__weak修饰符的变量赋给__strong修饰的变量就可以避免这种情况如:
|
|
这里只注册了一次自动释放池
因此在@autoreleasePool块结束之前都可以放心使用__weak修饰符的变量
对象释放过程:objc_release执行步骤
- 调用
objc_release - 因为引用计数为0所以执行
dealloc - 调用
_objc_rootDealloc - 调用
object_dispose - 调用
objc_destructInstance - 调用
objc_clear_deallocating
1.从
weak表中获取废弃对象的地址为键值的记录
2.将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil
3.从weak表中删除该记录
4.从引用计数表中删除废弃对象的地址为键值的记录
使用__weak修饰符时,以下源代码会引起编译警告
|
|
上述代码,内部实现如下:
内部实现如下:
|
|
这段代码会引发警告,因为编译器判断生成并持有的对象并不能继续持有,__weak是不持有对象的.所以对象会被立即通过objc_release函数释放和废弃。obj则会为nil
使用_unsafe_unretained也是一样的效果,只是不会把对象置为nil,而是成为一个野指针
__autoreleasing修饰符
将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法
|
|
引用计数
- 使用
__strong修饰符的对象引用计数+1 - 使用
__autoreleasing修饰符的对象引用计数+1 - 使用
__weak修饰符的对象,由于弱引用并不持有对象本身,所以引用计数不变