《iOS与OS X多线程和内存管理》阅读笔记
什么是引用计数
书上举了一个很简单的例子:办公室照明。需要照明的人数即为引用计数。
运作过程
(1)第一个人进入办公室,“需要照明的人数”加1。计数值从0变成1,因此需要开灯。
(2)之后每当有人进入办公室,“需要照明的人数”就加1。如计数值从1变成2。
(3)每当有人下班离开办公室,“需要照明的人数”就减1。如计数值从2变成1。
(4)最后一个人下班离开办公室时,“需要照明的人数”减1。计数值从1变成了0。因此关灯。
办公室照明设备所做动作和OC的对象所作的动作对照表
对照明设备所做的动作 | 对Objective-C对象所做的动作 |
---|---|
开灯 | 生成对象 |
需要照明 | 持有对象 |
不需要照明 | 释放对象 |
关灯 | 废弃对象 |
一般情况下,OC中alloc,new,copy,mutableCopy等开头的函数表示该方法生成并持有该对象
其他情况需要retain才可以获得对象
GNUstep中NSAllocateObject的实现
struct objc_layout {
NSUInteger retained;
};
inline id
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
{
int size = 计算容纳对象所需内存大小;
id new = NSZoneMalloc(zone, size);
memset(new, 0, size);
new = (id)&((struct objc_layout *) new)[1];
}
这种结构在对象中包含了一个struct,其中为一个NSUInteger属性,用于记录retained的数量。
而当其调用retain时retained便会执行加1
下面是书中猜想的apple的实现方法
作者通过断点调试发现retainCount/retain/release的执行如下
-retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
-retain
__CFDoExternRefOperation
CFBasicHashAddValue
-release
__CFDoExternRefOperation
CFBasicHashRemoveValue
这其中都调用了同一个方法:__CFDoExternRefOperation。对其代码实现猜想如下
int __CFDoExternRefOperation(uintptr_t op, id obj) {
CFBasicHashRef table = 取得对象对应的hash表(obj);
int count;
switch (op) {
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey(table, obj);
return count;
case OPERATION_retain:
CFBasicHashAddValue(table, obj);
return obj;
case OPERATION_release:
count = CFBasicHashRemoveValue(table, obj);
return 0 == count;
}
}
- (NSUInteger) retainCount
{
return (NSUInteger)__CFDoExternRefOperation(OPERATION_retainCount, self);
}
- (id) retain
{
return (id)__CFDoExternRefOperation(OPERATION_retain, self);
}
- (void) release
{
return __CFDoExternRefOperation(OPERATION_release, self);
}
所以按照这种猜想,引用计数在内存中是以一个hash表存在的。这个表对应的值包括对象的引用计数以及其内存地址。
两种实现方法比较
通过内存块头部管理引用计数的好处如下:
- 少量代码即可完成
- 能够统一管理引用计数用内存块与对象用内存块。
通过引用计数表管理引用计数的好处如下:
- 对象使用的内存块的分配不用考虑内存块的头部
- 引用计数表中记录的对象的内存块地址,可以从这个地址找到内存对象