美文网首页
OC-retain底层实现解读

OC-retain底层实现解读

作者: jayhe | 来源:发表于2020-09-21 18:23 被阅读0次

了解了release的底层实现之后,再来看看retain的内部实现

1. 内部调用流程

_objc_rootRetain

- (id)retain {
    return _objc_rootRetain(self);
}

rootRetain

NEVER_INLINE id
_objc_rootRetain(id obj)
{
    ASSERT(obj);

    return obj->rootRetain();
}

objc_object::rootRetain()
需要注意的是跟release一样,系统会对是否支持SUPPORT_NONPOINTER_ISA做不同的处理

  • 支持Nonpointer isa的处理
ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}
  • 不支持Nonpointer isa的处理
inline id 
objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}

可以看到不支持Nonpointer isa的处理就是直接sidetable_retain,这是由于计数都存储在sidetable中了,处理逻辑较支持Nonpointer isa的情况要简单一些;接下来分别看看内部实现

2. 内部实现

2.1 不支持Nonpointer isa的处理

不支持Nonpointer isa的架构一般就是32bit,isa指针就是纯粹的指针,而64bit的isa,一些位存储了额外的标记信息以及计数

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

逻辑也比较清晰,去sidetable取出计数信息,执行+1操作

2.2 支持Nonpointer isa的处理
由于Nonpointer isa,isa本身包含了计数信息,同时当计数超过isa的计数位能表示的范围,就会存储一部分到sidetable中去,所以处理的逻辑会复杂一点

objc-object.h中的objc_object::rootRetain(bool tryRetain, bool handleOverflow)方法实现如下

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow) // retain的传参数 false、false
{
    if (isTaggedPointer()) return (id)this; // tagged pointer直接返回

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) { // 支持Nonpointer isa的架构,但是isa没有额外信息
            // 引用计数存储在sidetable中,走sidetable的流程
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        // Nonpointer isa中存储有额外信息,包含引用计数
        uintptr_t carry; // 是否溢出的标记,1则表示溢出了
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        
        if (slowpath(carry)) { // 超过extra_rc所能表示的范围了
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) { // retain的实现handleOverflow传的是false,当溢出了会执行下面这个分支
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain); // 这里调用的也是该函数,只是handleOverflow传的是true,会走下面的流程
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true; // 标记需要将计数迁移到sidetable中去
            newisa.extra_rc = RC_HALF; // 将extra_rc设置为它可以表示的最大数的一半,另外一半则存储到sidetable中去
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF); // 将另外一半的计数存储到sidetable中去
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

整体流程
创建一个临时的isa:newisa,操作都是针对临时变量来操作,最后将newisa的bits信息存储到原始的isa中去,有个do-while去处理存储的逻辑,失败的话就重试

  • <1> 对于Nonpointer isa没有存储额外信息的,走sidetable_retain将sidetable中计数+1的流程
  • <2> 对于Nonpointer isa中存储额外信息的情况,尝试对isa中的extra_rc++
  • <2.1> extra_rc++没有溢出的情况,则将isa的值修改为extra_rc++之后的值
  • <2.2> extra_rc++溢出的情况,则处理overflow流程,将一半的计数存储到extra_rc,另一半存储到sidetable中去,同时设置标记位has_sidetable_rc为true
3.总结
  • 对于64bit的系统isa可以存储额外的信息,那么有extra_rc位用来存储引用计数;当超过它所表示的范围,就会存储计数到sidetable中
  • retain的流程也是针对isa是否是Nonpointer isa分别做了处理
  • 个人觉得巧妙的一点就是:当extra_rc存储溢出了,这个时候是一半(extra_rc能表示的最大值+1的一半)在extra_rc一半存储在sidetable中,这里跟release的操作是对应的(extra_rc不够减了也是去sidetable借extra_rc最大值的一半的计数),这样设计的好处避免了频繁的去sidetable中读取计数信息---假如我们溢出了把计数全部存到sidetable中去,那么有release的时候,extra_rc也不够减了,又去借,这就大大降低了效率,比起直接操作isa。

相关文章

网友评论

      本文标题:OC-retain底层实现解读

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