OC底层探索(二十四)内存管理 -- 引用计数底层分析

OC底层文章汇总

OC底层探索(十八)内存五大区中了解了栈区、堆区、全局区、常量区和代码区五大区的分布,那么今天来分析一下内存管理的引用计数。

taggedpointer小对象类型

在iOS中NSNumber、NSString都是有小对象组成的对象,存放在常量区,并且占用空间非常的

在分析ISA、cache、read_image和sendMessage时都提及到了小对象类型。

分析

  • _read_images中可以看到initializeTaggedPointerObfuscator方法,当时并没有分析,这是一步是进行初始化小对象类型。

在这里插入图片描述

  • 查看initializeTaggedPointerObfuscator

    • 在iOS14时,发现苹果将小对象类型的指针地址进行了混淆。
static void
initializeTaggedPointerObfuscator(void)
{
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation.
        DisableTaggedPointerObfuscation) {
        objc_debug_taggedpointer_obfuscator = 0;
    } else {
        // Pull random data into the variable, then shift away all non-payload bits.
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
    }
}
  • 在源码中发现了_objc_decodeTaggedPointer_objc_taggedPointersEnabled,分别是对小对象类型进行加密解密,即执行异或操作
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

static inline bool 
_objc_taggedPointersEnabled(void)
{
    extern uintptr_t objc_debug_taggedpointer_mask;
    return (objc_debug_taggedpointer_mask != 0);
}
  • 分别写NSString和NSNumber变量,并进行解密查看其地址。
NSNumber *number1 = @1;
NSString *str2 = [NSString stringWithFormat:@"b"];

NSLog(@"%@-%p-%@",object_getClass(number1),number1,number1);
NSLog(@"%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
NSLog(@"%p-%@",str2,str2);
 NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));

打印结果为:
在这里插入图片描述

  • _objc_isTaggedPointer中是进行判断是否为小对象类型,地址直接是对_OBJC_TAG_MASK进行与运算进行判断,其中_OBJC_TAG_MASK1 左移 63位,就是判断最高位是否为1,如果是1那么就是小对象类型。
static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
 define _OBJC_TAG_MASK (1UL<<63)
  • 其中小对象的类型是由地址的高2~4位决定的,其中010 => 2 代表的是NSString011 => 3代表的是NSNumber.

在这里插入图片描述

  • 分析0xb0000000000000120xa000000000000621

    • 0xa => 1011 最高位1代表的是小对象类型,011代表的是NSNumber类型
    • 0xb => 1010 最高位1代表的是小对象类型,010代表的是NSString类型

小结:

  • Tagged Pointer专⻔用来存储的对象,例如NSNumber和NSDate

  • Tagged Pointer 指针不再是地址了,而是真正的值。所以,实际上它不再 是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储 在堆中,所以retainrelease 没有对小对象类型进行内存管理,即可以自主回收

  • 内存读取上有着3倍的效率,创建时比以前快106倍。

SiddeTables散列表

NONPOINTER_ISA中引用计数是存储在extra_rc中,但是如果当这里面存满了,那么就向SiddeTables中存储.

  • 应用中存在多张散列表

在这里插入图片描述

  • 每张散列表中都存放着自旋锁引用计数弱引用表.

在这里插入图片描述

源码分析

  • SideTable是存在到SideTablesMap (hash表)中
static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}
  • StripedMap中我们发现SideTable会有多张,

    • 真机最多有8张散列表
    • 模拟器最多有64张散列表
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR//真机、非模拟器
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];

    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }

  
    
......
};
  • SideTable是一个结构体,分别存放着spinlock_tRefcountMap和`weak_table_t类型的属性。
struct SideTable {
    spinlock_t slock;//自旋锁
    RefcountMap refcnts;//引用计数
    weak_table_t weak_table;//弱引用表

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

retain 引用计数加一

  • 搜索objc_retain,发现在源码中,对小对象类型进行了判断,如果是小对象类型就直接返回该对象没有进行引用计数加一
__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
  • 进入retain,查看rootRetain
inline id 
objc_object::retain()
{
    ASSERT(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
  • 查看rootRetain
ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //判断是否为nonpointerISA,
        if (slowpath(!newisa.nonpointer)) {
        //不是nonpointerISA直接操作散列表
            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;
        }
        
        uintptr_t carry;
        //newisa的bits 加一,其中carry
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        //如果isa中引用计数满了就操作散列表,直接将extra_rc的一半存储到散列表中
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // 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;
            newisa.extra_rc = RC_HALF;
            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);
    }

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

retain操作流程

  • 判断是否为nonpointerISA

    • 不是nonpointerISA,直接操作散列表

    • nonpointerISA,在进行判断是否当前对象是否正在释放

      • 正在释放:直接返回,不进行操作

      • 没有在释放引用计数 加一,存储到isaextra_rc中,如果extra_rc 满了,就将其中一半存储到散列表中。

流程图
在这里插入图片描述

realse 引用计数减一

  • 通过setProperty -> reallySetProperty -> objc_release -> release -> rootRelease -> rootRelease顺序,进入rootRelease源码,其操作与retain 相反

    • 判断是否是Nonpointer isa,如果不是,则直接对散列表进行-1操作

    • 如果 Nonpointer isa,则对extra_rc中的引用计数值进行-1操作,并存储此时的extra_rc状态到carry

    • 如果此时的状态carray0,则走到underflow流程

    • underflow流程有以下几步:

      • 判断散列表是否 存储一半引用计数

      • 如果,则从散列表取出存储的一半引用计数,进行-1操作,然后存储到extra_rc

      • 如果此时extra_rc 没有值,散列表中也是的,则直接进行析构,即dealloc操作,属于自动触发

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //判断是否是Nonpointer isa
        if (slowpath(!newisa.nonpointer)) {
            //如果不是,则直接操作散列表-1
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        //进行引用计数-1操作,即extra_rc-1
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        //如果此时extra_rc的值为0了,则走到underflow
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;
    //判断散列表中是否存储了一半的引用计数
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.
        //从散列表中取出存储的一半引用计数
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            //进行-1操作,然后存储到extra_rc中
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }
    //此时extra_rc中值为0,散列表中也是空的,则直接进行析构,即自动触发dealloc流程
    // Really deallocate.
    //触发dealloc的时机
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        //发送一个dealloc消息
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

retainCount

面试题:alloc创建的对象的引用计数为多少?

NSObject *objc = [NSObject alloc];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));

打印结果是:
在这里插入图片描述
但是此时objc的引用计数为0

疑问:为什么objc的引用计数为0,但是RetainCount打印出来是1呢?

问题出在retainCount的实现中!!!

  • 进入retainCount -> _objc_rootRetainCount -> rootRetainCount源码,其实现如下
 - (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}
👇
uintptr_t
_objc_rootRetainCount(id obj)
{
    ASSERT(obj);

    return obj->rootRetainCount();
}
👇
inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    //如果是nonpointer isa,才有引用计数的下层处理
    if (bits.nonpointer) {
        //alloc创建的对象引用计数为0,包括sideTable,所以对于alloc来说,是 0+1=1,这也是为什么通过retaincount获取的引用计数为1的原因
        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();
}
  • 由源码中可以看出来,retaincount是有 bits.extra_rc + sidetable_getExtraRC_nolock + 1得到。

  • 综上所述,alloc创建的对象实际的引用计数为0,其引用计数打印结果为1,是因为在底层rootRetainCount方法中,引用计数默认+1了,但是这里只有对引用计数的读取操作,是没有写入操作的,简单来说就是:为了防止alloc创建的对象被释放(引用计数为0会被释放),所以在编译阶段,程序底层默认进行了+1操作。实际上在extra_rc中的引用计数仍然为0

小结

  • alloc创建的对象没有 retainrelease

  • alloc创建对象的引用计数为0,会在编译时期,程序默认加1,所以读取引用计数时为1

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值