内存管理/引用计数
思考方式
- 自己生成的对象,自己所持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
对象操作 | OC方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy |
持有对象 | retain |
释放对象 | release |
废弃对象 | dealloc |
非自己生成的对象,想持有得通过 retain 方法
使用以下名称开头的方法也意味着自己生成并持有对象: allocMyObject/newThatObject/copyThis/mutableCopyObject
//自己生成并持有的对象
let obj = [[NSObject alloc] init];
let obj1 = [NSObject new];
//自己持有对象
[obj release];
[obj1 release];
//释放对象,指针仍保留,貌似能访问,释放的对象绝对不可访问
//非自己生成的对象
let obj2 = [NSMutableArray array];
//对象存在,但不持有
[obj2 retain];
//持有对象
[obj2 release];
//释放
生成对象方法的源代码实现:
//自己生成并持有
- (id)allocObject
{
id obj = [[NSObject alloc] init];
return obj
}
//生成但不持有
- (id)object
{
id obj = [[NSObject alloc] init];
//autorelease可以使取得的对象存在,但不持有
[obj autorelease];
return obj
}
autorelease 使对象在超出指定生存范围时能自动并正确释放(调用 release ) release 方法会立即释放对象,而 autorelease 则是把对象注册到 autoreleasepool 中,pool 结束时自动调用 release
释放非自己持有的对象程序会奔溃
let obj = [[NSObject alloc] init];
[obj release];
[obj release];
//奔溃
//情况:再次废弃已废弃的对象时崩溃;访问已废弃对象时奔溃
let obj1 = [NSObject object];
[obj1 release];
//奔溃
alloc/retain/release/dealloc实现
GNUStep 中 alloc 是通过 allocWithZone: 类方法调用 NSAllocateObject 函数分配对象,NSAllocateObject 函数通过调用 NSZOneMallioc 函数分配存放对象所需的内存空间,之后再将内存空间置 0,最后返回作为对象而使用的指针
NSZone 是为防止内存碎片化而引入的结构,对内存分配的区域本身进行多重化管理,根据对象的目的,大小分配内存,从而提高管理效率(但是现在的运行系统内存管理本身已极具效率,没啥卵用
去掉 NSZone 后,alloc 类方法用 struct obj_layout 中的 retained 整数来保存引用计数,并将其写入对象内存头部,将对象内存块全部置 0 后返回
//alloc简化版
struct obj_layout{
NSUInteger retained;
//[obj retainCount]可获取该值
}
+ (id)alloc
{
int size = sizeof(struct obj_layout)+对象大小;
struct obj_layout *p = (struct obj_layout *)calloc(1,size);
return (id)(p+1);
}
calloc 方法在内存的动态存储区中分配 n 个长度为 size 的连续空间,函数返回一个指向分配起始地址的指针,分配完内存后,自动初始化该内存空间为零( malloc 则不初始化,里边数据是随机的垃圾数据)
retain 方法使 retained 变量加一,release 则减一,当 retained 等于0时 dealloc,dealloc 时进行 free()
苹果的实现
苹果是将引用计数保存在引用计数表(散列表)的记录里(表键值为内存块地址的散列值)
通过内存块头部管理引用计数的好处:
- 少量代码即可完成
- 能统一管理引用计数用内存块与对象用内存块
通过引用计数表管理的好处:
- 对象用内存块无需考虑内存块头部
- 引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块(即使出现故障导致对象内存块损坏,也可以通过计数表确认位置)
autorelease
有点类似于 C 语言中局部变量的特性,超过作用域就自动废弃,即对象实例的release 方法被调用,与 C 语言不同的是,可以设定变量的作用域
- 生成 NSAutoreleasePool 对象
- 调用已分配对象的 autorelease 实例方法
- 废弃 NSAutoreleasePool 对象( object 自动调用 release )
NSAutoreleasePool 生存周期相当于 C 语言变量的作用域
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain] //等同于[obj release]
相当于程序主循环的 NSRunLoop 会自动生成,持有并废弃NSAutoreleasePool 对象,不需要非得使用 NSAutoreleasePool 对象,但在大量生成 autorelease 对象时,可能会内存不足,有必要手动在适当地方生成,持有并废弃 NSAutoreleasePool
有很多类方法用于返回 autorelease 对象:[NSMutableArray arrayWithCapacity:1]
autorelease 实现
GNUStep 中 autorelease 实例方法实质是调用 NSAutoreleasePool 对象的addObject 类方法
GNUStep 实际是用称为“ IMP Caching ”的方法实现的,在进行方法调用时,在框架初始化时对类名/方法名,以及取得方法运行时的函数指针的结果值进行缓存,速度是直接调用 [NSAutoreleasePool addObject: self] 方法的两倍
addObject 类方法调用正在使用的 NSAutoreleasePool 对象的 addObject 实例方法,使用的是连接列表,调用 autorelease 方法的对象被追加到NSAutoreleasePool 对象的数组中。drain 实例方法则先 realse array 里的每一个对象,再 release array
苹果的实现
和 GUNStep 完全相同,可通过 [NSAutoreleasePool showPools] 确定已被autorelease 的对象的状况
autorelease NSAutoreleasePool 对象会发生异常