美文网首页
Objective-C 引用计数原理

Objective-C 引用计数原理

作者: Crazy2015 | 来源:发表于2019-05-17 16:35 被阅读0次
引用计数如何存储

如果对象支持使用 TaggedPointer,苹果会直接将其指针值作为引用计数返回;

如果当前设备是 64 位环境并且使用 Objective-C 2.0,那么“一些”对象会使用其 isa 指针的一部分空间来存储它的引用计数;

否则 Runtime 会使用一张散列表来管理引用计数。

case1:TaggedPointer

指针值作为引用计数返回

inline bool 
objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}
判断当前对象是否在使用 TaggedPointer 是看标志位是否为 1 :

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

isa 指针(NONPOINTER_ISA)

非Tagged Pointer,且开启了指针优化,此时引用计数先从extra_rc中去取(这里将取出来的值进行了+1操作,所以在存的时候需要进行-1操作),接着判断是否有SideTable,如果有再加上存在SideTable中的计数;

用 64 bit 存储一个内存地址显然是种浪费,毕竟很少有那么大内存的设备。于是可以优化存储方案,用一部分额外空间存储其他内容。isa 指针第一位为 1 即表示使用优化的 isa 指针,这里列出不同架构下的 64 位环境中 isa 指针结构:

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1;     // no r/r overrides
    // uintptr_t lock : 2;        // lock for atomic property, @synch
    // uintptr_t extraBytes : 1;  // allocated with extra bytes

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)
    };

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif

SUPPORT_NONPOINTER_ISA 用于标记是否支持优化的 isa 指针,其字面含义意思是 isa 的内容不再是类的指针了,而是包含了更多信息,比如引用计数,析构状态,被其他 weak 变量引用情况。

判断方法也是根据设备类型:

// Define SUPPORT_NONPOINTER_ISA=1 to enable extra data in the isa field.
#if !__LP64__  ||  TARGET_OS_WIN32  ||  TARGET_IPHONE_SIMULATOR  ||  __x86_64__
#   define SUPPORT_NONPOINTER_ISA 0
#else
#   define SUPPORT_NONPOINTER_ISA 1
#endif

综合看来目前只有 arm64 架构的设备支持,下面列出了 isa 指针中变量对应的含义:

变量名 含义
indexed 0 表示普通的 isa 指针,1 表示使用优化,存储引用计数
has_assoc 表示该对象是否包含 associated object,如果没有,则析构时会更快
has_cxx_dtor 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快
shiftcls 类的指针
magic 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化。
c weakly_referenced 表示该对象是否有过 weak 对象,如果没有,则析构时更快
deallocating 表示该对象是否正在析构
has_sidetable_rc 表示该对象的引用计数值是否过大无法存储在 isa 指针
extra_rc 存储引用计数值减一后的结果

非Tagged Pointer,且开启了指针优化,此时引用计数先从extra_rc中去取(这里将取出来的值进行了+1操作,所以在存的时候需要进行-1操作),接着判断是否有SideTable,如果有再加上存在SideTable中的计数;

非Tagged Pointer,没有开启了指针优化

使用sidetable_retainCount()函数返回

uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

逻辑就是根据当前对象的作为键,从SideTables中取出对应的SideTable 对象,其 refcnts 属性就是之前说的存储引用计数的散列表,
然后在引用计数表中用迭代器查找当前实例对应的键值对,获取引用计数值,并在此基础上 +1 并将结果返回。这也就是为什么之前说引用计数表存储的值为实际引用计数减一。

参考:
https://juejin.im/post/5c761e41f265da2dc13c908c

http://yulingtianxia.com/blog/2015/12/06/The-Principle-of-Refenrence-Counting/

相关文章

网友评论

      本文标题:Objective-C 引用计数原理

      本文链接:https://www.haomeiwen.com/subject/prtgaqtx.html