iOS底层探索--内存管理(上)

  • 兄弟们,最近实在是太忙了。不过~我又回来继续探索了。

  • 内存管理这个名词,我相信所有的iOS工程师都听说过,也是大多数兄弟们,面试最头疼的,今天!小谷带大家走一波源码。希望对大家有所帮助。

  • 关于内存管理,大家都会想到,ARC/MRC、retain、release、dealloc、autorelease。今天就浅谈一波。不对的地方,我在查源码找找,哈哈~

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

ARC/MRC  (我就不多说了)
ARC(Automatic Reference Count ) :自动的引用计数
其实ARC 是LLVM和runtime 结合的产物。
不让开发者主动调用dealloc/relesae/retain。
并且引进了,weak和strong,

1. retain 和 release

  • 大家对于retain和release一定不陌生。我当定义一个属性的时候就会产生,setter和getter方法。赋值的操作就是新值的retain和旧值的release。源码如下:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

今天我们研究下他是如何retain的。

1.1. 探究retain
首先,我们点进方法看看:

__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

我不太知道怎么把关键字设置高亮,兄弟们对不起了。

  1. 这个判断是:如果是isTaggedPointer就返回。否则就进行retain操作
  2. 那这是不是说明了:isTaggedPointer类型,不会进行内存管理,会自动释放
  3. 那我们就简单的看下这个isTaggedPointer

1.1.1. 拓展isTaggedPointer类型

  1. taggedpointer就是大家常说的小对象,我们来看下他的实现:

// 左移63位,就只是保留了最高位
#   define _OBJC_TAG_MASK (1UL<<63)

inline bool 
objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}

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


  1. 所以判断小对象就是判断最高位。

这个不是内存管理的重点,主要是遇到了,就提一嘴,taggedpointer类型是不进行内存管理的。不会retain和release。一般的小对象是NSNumber、NSData和小于11位的NSString

1.2. retain的实现
我们看一下retain的实现:下面是我追踪源码的过程:

  1. 这样我们就找到了retain 源码的实现(我在里面已经加了注释(虽然我知道兄弟们肯定可以看懂。。))
ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;


    bool sideTableLocked = false;
    bool transcribeToSideTable = false;


	//isa  的原因是要在extra_rc存值(如果有不了解isa结构的兄弟可以看下我的博客)
    isa_t oldisa;
    isa_t newisa;


    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
	//slowpath就是小几率走的意思。
	//不是nonpoint_isa
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);//清理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)) {//如果是dealloc了,(这个一会我会在说)
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();//解锁
            return nil;//返回nil
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++ (这个英文注释不是我写的,哈哈)


        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {//查看是否溢出了(就是满了没有)
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);//没有满的话,就retain操作
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            //如果满了,就会把extra_rc的一半放入散列表
            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;
}

//这个是中间有调用的,其实就是又调用回来了。
NEVER_INLINE id 
objc_object::rootRetain_overflow(bool tryRetain)
{
    return rootRetain(tryRetain, true);
}

我在里面加了一点注释,其实这个大家可能有些有疑惑,不知道散列表是什么。听起来高端大气,其实就是一张用来存储的表

1.3. retain 总结

我来根据上述代码总结下。(我知道兄弟们都看的懂,不过我比较笨 😆)

  1. 进行retain操作时,先判断是否是taggedpointer类型。是的话继续retain操作,taggedpointer不受内存管理

  2. 进行retain时首先操作的是isa中的extra_rc,(如果不明白isa结构可以看下我的博客isa结构分析)

  3. 进行retain时会判断是否dealloc,如果被释放掉就返回nil

  4. 进行retain操作会使 extra_rc++, 如果溢出的话,会把一半存在散列表中

1.4. release实现

  1. 大家看完retain之后,release就相对简单了很多,其实就是个反向操作
    我把源码贴出来啦(这个比较长,但操作差不多):
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;
        if (slowpath(!newisa.nonpointer)) {
            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;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        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.
            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.
        }
    }

    // Really deallocate.

    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) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

这个就留给兄弟们看了,我就不做注释污染了(其实就是isa中的extra_rc进行减减的操作)

2. dealloc

  1. 在retain和release代码中,它会判断对象是否dealloc
  2. 我们浅谈一波。(其实就是个析构~)
  3. 首先大家可以思考下,假如我要释放了,我需要怎么办?
  4. 我贴源码了啊。
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
  1. 是否是isa.nonpointer,只有是这才会有释放这回事哈
  2. 是否有弱引用表
  3. 是否有关联对象
  4. 是否有C++的析构,就是哪个cxx_dtor (这个也在isa结构分析的博客中有提到)
  5. 是否有散列表

其实就是这5个条件判断
否则就会调用object_dispose(其实也是发现有以上判断就处理的操作)
代码给兄弟们奉上~

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

好了兄弟们,就先到这了~ 希望可以给大家带来帮助。洗完了我继续回去加班~😔

原文作者:小谷先森
原文地址:https://juejin.cn/post/6899420127050072077

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值