《Objective-C 高级编程》 1.2.3节 alloc/retain/release/dealloc 实现——学习总结
《Objective-C 高级编程》 1.2.3节 alloc/retain/release/dealloc 实现——学习总结
更新记录
时间 | 版本修改 |
---|---|
2020年4月23日 | 初版 |
2020年4月25日 | 更正:苹果的实现方式并不是在NSObejct基类中用一个字段记录引用计数,而是使用散列表的方式 |
前言
- 本人在阅读《Objective-C 高级编程》 1.2.3节 alloc/retain/release/dealloc实现这个章节时,有一个细节一只没搞懂
- 这个问题阻塞了我的阅读进度,直至后续和一位好友交流时才彻底搞懂了这个问题(虽然本质确实是个很简单的问题)。
我的疑惑
- 按照我们之前泛读该书的得到常用结论,大致如下
- ARC模式下,NSObejct使用引用计数的方式自动管理内存
- 其原理大致如“N个人进入办公室,开灯,N个人离开办公室,关灯”这个鲜明的例子
- 所以,生成一个NSObject对象,其引用计数应该是1。
- 我的问题
- 但是当我看到下面release的实现代码时,感觉到非常的困惑
- (void)release { if (NSDecrementExtraRefCountWasZero(self)) { [self dealloc]; } } //这是一个C语言的函数 BOOL NSDecrementExtraRefCountWasZero(id anObject) { if (((struct obj_layout *)anObject)[-1].retained == 0) { return YES; } else { ((struct obj_layout *)anObject)[-1].retained--; return NO; } }
- 为1的时候应该就要释放才对了呀,为什么引用计数为1的时候,还要执行减1操作,变成0呢?
指点迷津
- 之所以有这样的困惑,是因为犯了一个先入为主的错误:
- 真实的引用计数一定是从1计数的吗?0是代表什么呢?
- 看到作者给出的
retainCount
函数:- (NSUInteger)retainCount { return NSExtraRefCount(self) + 1; } //C语言函数 inline NSUInteger NSExtraRefCount(id anObject) { return ((struct obj_layout *)anObject)[-1].retained; }
- 也就是说,结构体的成员变量
retained
是从0开始计数的。- 初始化对象的时候,初始化为0,就已经表示这个对象目前被持有一次了。
- 如果再被持有一次,会+1,变成1
- 如果这个时候释放,见上述的
NSDecrementExtraRefCountWasZero
函数,因为等于0,所以调用dealloc
销毁了该对象。 retainCount
函数返回值,手动加上了1,契合了人类本身的思考方式。(所以千万不要像我一样被误导了)
- 注意
- 这里是作者剖析 GNUstep 源代码得来的,自动引用计数的实现方式,和苹果,以及由于时光匆匆,实现机制不一定完全一样。但是基本思想是不变的。(目前苹果最新的实现方式,是利用散列表记录每个对象的引用计数,不在作为NSObject类的字段,减小了对象的内存)。
后记
- 搞清楚每一个细节,才能一点一点堆砌基础,乃至堆起整个知识体系。