《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类的字段,减小了对象的内存)。

后记

  • 搞清楚每一个细节,才能一点一点堆砌基础,乃至堆起整个知识体系。