内存管理/引用计数 - alloc/retain/release
所有内容引用自
《Objective-C 高级编程 iOS与OS X多线程和内存管理》
,加入了自己的部分理解。
本节小结,点小1
跳到底部[1]
第一节、思考方式
第三节、autorelease实现
这里分两种实现,GNUstep
实现,苹果的实现。
为啥要研究GNUstep
实现?Cocoa
不开源,没办法知道苹果实现。GNUstep
开源,并且是Cocoa
的互换框架。
GNUstep
实现
1、alloc
实现
// GNUstep/modules/core/base/Source/NSObject.m
+ (id) alloc{
return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id)allocWithZone:(NSZone *)z{
return NSAllocateObject (self, 0 , z);
}
struct obj_layout{
NSUInteger retained;
};
inline id
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone){
int size = 计算容纳对象所需的内存大小;
// 先分配内存
id new = NSZoneMalloc(zone, size);
// 内存置0
memset(new, 0, size);
// 返回指针
new = (id) & ((struct obj_layout *) new)[1];
}
NSZone
是防止内存碎片化引入的结构。对内存分配的区域进行多重化管理,根据使用对象的目的、对象大小分配内存,提高内存管理的效率。(就是小内存是一段连续空间,大内存是一段连续空间。)
因为iOS
运行时系统的内存管理已经极具效率,所以目前已经忽略了区域的概念。
alloc
简化区域后的实现:
struct obj_layout{
NSUInteger retained;
};
+ (id) alloc{
int size = sizeof(struct obj_layout) + 对象大小;
struct obj_layout *p = (struct obj_layout *) calloc(1, size);
return (id)(p + 1);
}
其中retained
保存引用计数,写入到对象头部。
对象引用计数,可以使用retainCount
获取到。实现如下:
- (NSUInteger)retainCount{
return NSExtraRefCount(self) + 1;
}
inline NSUInteger
NSExtraRefCount(id anObject){
// 由对象寻址找到对象头部,取出retained值返回
return ((struct obj_layout *) anObject) [-1].retained;
}
2、retain
实现
- (id)retain{
NSIncrementExtraRefCount(self);
return self;
}
inline void
NSIncrementExtraRefCount(id anObject){
// 如果超过最大值,给出提示
if(((struct obj_layout *) anObject) [-1].retained == UNNT_MAX - 1){
[NSException raise:NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];
}else{ // +1
((struct obj_layout *) anObject) [-1].retained++;
}
}
3、release
实现
- (void)release{
if (NSIncrementExtraRefCountWasZero(self)){
[self dealloc];
}
}
BOOL
NSIncrementExtraRefCountWasZero(id anObject){
if (((struct obj_layout *) anObject) [-1].retained == 0){
return YES;
}else{
((struct obj_layout *) anObject) [-1].retained --;
return NO;
}
}
4、dealloc
实现
- (void)dealloc{
NSDeallocateObject(self);
}
inline void
NSDeallocateObject(id anObject){
struct obj_layout *o = &((struct obj_layout *) anObject[-1];
free(o);
}
小结:
-
Objective-C
对象中存有引用计数整数值 -
alloc
或retain
,引用计数+1 -
release
引用计数-1 - 引用计数为0时,调用
dealloc
废弃对象
苹果实现
NSObject
源码没有公开,只能通过断点来追踪程序的执行。
1、alloc
的实现
+alloc
+allocWithZone:
// 创建对象
class_createInstance
// 分配内存
calloc
2、retainCount/retain/release
的实现
// retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
// retain
__CFDoExternRefOperation
CFBasicHashAddValue
// release
__CFDoExternRefOperation
CFBasicHashRemoveValue
// CFBasicHashRemoveValue返回0时,会调用dealloc
CF
前缀,是使用了Core Foundation
框架。
__CFDoExternRefOperation
在CFRuntime.c
中。便于理解,简化实现如下。
int __CFDoExternRefOperation(uintptr_t op,id obj){
CFBasicHashRef table = 取得对象对应的哈希表(obj);
int count;
switch(op){
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey(table, obj);
return count;
case OPERATION_retain:
CFBasicHashAddValue(table,obj);
case OPERATION_release:
count = CFBasicHashRemoveValue(table,obj);
return 0 == count;
}
}
大致可以得出,采用的是哈希表来管理引用计数的,表健值是内存块地址。
前面GNUstep
实现,是把引用计数放在对象内存块头部。和苹果相比各自的好处:
✨✨✨
放在头部的好处:
- 节约代码量。少量代码就可以实现
- 能统一管理引用计数内存块和对象内存块。
✨✨✨✨✨
使用哈希表作为引用计数表的好处:
- 对象内存块分配不再考虑内存块头部。
- 哈希表存有内存块地址,可以追溯到各个对象的内存块。
第二点特别重要,即使内存块损坏了,只要引用计数表没有破坏,就可以确认内存块的位置。特别是检测内存泄露时,可以帮助定位各对象是否持有。
小结
1、可以通过GNUstep
源码来推测苹果实现。
2、alloc\retain
引用计数+1。
3、release
引用计数-1,为0时,调用dealloc
废弃对象。
4、引用计数由哈希表管理,方便追溯内存块,定位内存泄露。
-
😊假装是锚点的脚注 ↩