您的当前位置:首页正文

Objective-C引用计数实现

来源:华佗小知识

《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表存在的。这个表对应的值包括对象的引用计数以及其内存地址。

两种实现方法比较

通过内存块头部管理引用计数的好处如下:

  • 少量代码即可完成
  • 能够统一管理引用计数用内存块与对象用内存块。

通过引用计数表管理引用计数的好处如下:

  • 对象使用的内存块的分配不用考虑内存块的头部
  • 引用计数表中记录的对象的内存块地址,可以从这个地址找到内存对象