美文网首页
Objc源码之引用计数实现

Objc源码之引用计数实现

作者: 繁星mind | 来源:发表于2019-08-28 21:26 被阅读0次

    Objc源码之对象创建alloc和init
    Objc源码之initialize实现
    Objc源码之Load方法实现
    Objc源码之NSObject和isa
    Objc源码之引用计数实现
    objc源码之Method消息发送

    前言

       我们都知道OC的内存管理是引用计数,可是对象的引用计数是存储在哪里的呢?今天我们就从源码入手,来揭开这个谜底,下面我们就从对象创建开始说起。

    一、从alloc和retainCount引用计数

    首先我们看下retainCount方法,这个是获取引用计数的方法:

    - (NSUInteger)retainCount {
        return ((id)self)->rootRetainCount();
    }
    
    inline uintptr_t objc_object::rootRetainCount()
    {
        if (isTaggedPointer()) return (uintptr_t)this;
    
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        if (bits.nonpointer) {
            uintptr_t rc = 1 + bits.extra_rc;
            if (bits.has_sidetable_rc) {
                rc += sidetable_getExtraRC_nolock();
            }
            sidetable_unlock();
            return rc;
        }
    
        sidetable_unlock();
        return sidetable_retainCount();
    }
    

    看到这里,我们会发现引用计数包含三部分:
    1. isTaggedPointer,是否是Tagged Pointer类型
    2. nonpointer 是否是优化的isa指针。
    3. sidetable_retainCount 其它类型

    1.isTaggedPointer的情况

    Tagged Pointer是苹果在64位系统之后,用来优化内存的。
    1.Tagged Pointer专门用来存储小的对象,例如NSString、NSNumber、NSDate等;
    2.Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free;
    3.在内存读取上有着3倍的效率,创建时比以前快106倍。
    具体可以看看这个iOS Tagged Pointer (源码阅读必备知识) - 掘金

    总结就是isTaggedPointer情况下引用计数返回的是对象本身。

    2.nonpointer优化的情况

    nonpointer表示是否开启指针优化,

    • 0表示isa_t没有开启指针优化,不使用isa_t中定义的结构体。
    • 1表示isa_t开启指针优化,不能直接访问objc_object的isa成员变量 ,isa中包含了类信息、对象的引用计数等信息。

    要了解nonpointer,首先看alloc是怎么创建一个对象,其中nonpointerextra_rc都是走嗯么赋值的,下面是简化的alloc函数的调用过程:

    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    
    id  _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
          ...
          id obj = class_createInstance(cls, 0);
          return obj;
          ...
    }
    
    id class_createInstance(Class cls, size_t extraBytes)
    {
        return _class_createInstanceFromZone(cls, extraBytes, nil);
    }
    
    static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                                  bool cxxConstruct = true, 
                                  size_t *outAllocatedSize = nil)
    {
            ...
            obj = (id)calloc(1, size);
            if (!obj) return nil;
            obj->initInstanceIsa(cls, hasCxxDtor);
            return obj;
    }
    
    inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        initIsa(cls, true, hasCxxDtor);
    }
    
    inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
    { 
            ...
            newisa.bits = ISA_MAGIC_VALUE;
            newisa.has_cxx_dtor = hasCxxDtor;
            newisa.shiftcls = (uintptr_t)cls >> 3;
            isa = newisa;
    }
    
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          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
    
    

    我们总结一下调用过程alloc->_objc_rootAlloc->callAlloc->class_createInstance->_class_createInstanceFromZone->initIsa
    关于引用计数的部分,在最后初始化isa指针中initIsa,下面我们看下initIsa中的具体过程:

    newisa.bits = ISA_MAGIC_VALUE;
    

    在给bits赋值的时候,通过ISA_MAGIC_VALUE这个宏来赋值的,arm64下这个宏的值是0x000001a000000001ULL,通过ISA_BITFIELD,我们来看下具体含义:

    #   define ISA_BITFIELD                                                      \
          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
    

    extra_rc代表的就是对象的引用计数,它包含19位,nonpointer只包含一位,下面我们看下arm64下ISA_MAGIC_VALUE:

    ISA_MAGIC_VALUE
    紫色部分是extra_rc的值,我们看到extra_rc的值是0,我们知道,刚创建的对象,应用计数应该是1,但是这里怎么是0,那是因为extra_rc保存的是引用计数-1的值,在获取的时候,会进行+1.在arm64下nonpointer默认是1,默认开启isa指针优化的。

    nonpointer情况下,我们可以看到rootRetainCount代码包含两部分:

        if (bits.nonpointer) {
            uintptr_t rc = 1 + bits.extra_rc;
            if (bits.has_sidetable_rc) {
                rc += sidetable_getExtraRC_nolock();
            }
            sidetable_unlock();
            return rc;
        }
    

    第一部分就是上面说的extra_rc,获取到extra_rc以后,在又会判断has_sidetable_rc的值。
    has_sidetable_rc是用来做什么的?
    has_sidetable_rc是用来判断extra_rc是否存储不下引用计数数量的,如果引用计数大于extra_rc的最大存储数量,那么就会超出的引用计数存储到SideTables,下面看下源码:

    size_t objc_object::sidetable_getExtraRC_nolock()
    {
        assert(isa.nonpointer);
        SideTable& table = SideTables()[this];
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()) return 0;
        else return it->second >> SIDE_TABLE_RC_SHIFT;
    }
    

    了解weak原理的话,应该会熟悉SideTables,这也是weak对象存储的地方,上面的代码是取的SideTablesrefcnts中以对象为key的值,这里面refcnts是一个哈希表。

    struct SideTable {
        spinlock_t slock;
        RefcountMap refcnts;
        weak_table_t weak_table;
    }
    
    typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
    

    总结:如果extra_rc能够存储下引用计数,就只使用extra_rc,如果存储不下,就会使用SideTables进行辅助存储。

    3.sidetable_retainCount的情况

    sidetable_retainCount是在没有开启指针优化的情况,对象的引用计数直接存储在SideTable中。

    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;
    }
    
    

    二、总结:

    1. Tagged Pointer情况,直接返回对象指针
    2. 优化的isa指针,也就是nonpointer为1时,通过extra_rc和SideTables一起来管理引用计数。
    3.未开启指针优化,也就是nonpointer为0时,直接使用SideTables来管理引用计数 。

    参考:
    objc4-750源码
    OC内存管理--引用计数器
    【译】采用Tagged Pointer的字符串
    从 NSObject 的初始化了解 isa.md
    isa详解

    相关文章

      网友评论

          本文标题:Objc源码之引用计数实现

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