iOS-内存管理

2022/6/30 5:19:30

本文主要是介绍iOS-内存管理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、内存分布

当程序运行时,系统会开辟内核区、用户区。

值类型:存入栈;无需进行内存管理
引用类型:存入堆;无需进行内存管理

二、iOS的内存管理方案

2.1 Tagged Pointer(无需进行引用计数)

  • Tagged Pointer 是一个指针,指针中包含Tagged标记,用于区分存储的数据类型,同时将值也存储在指针中,通过位运算将其编码成一个指针格式

  • 针对小对象(存储内容小):NSDate、NSNumber、NSString、NSIndexPath 等
  • 使用后不再存储地址,而是真正的值;3倍的空间效率,106倍的访问速度
  • 在0的位置打上标记(Intel最低位为1,ARM最高位为1)
  • 初始化时会与一个随机值进行混淆

2.2 举例说明Tagged Pointer

以 NSString 为例,读取一个常规的 NSString,通过 栈区存储的指针地址,找到堆区空间,再从堆区读取到字符串的值,整个读取流程效率较低,所以,系统对其进行优化,如果 NSString 存储的字符串长度较短(<= 9,会使用 Tagged Pointer 存储。   三、引用计数 在之前我们探索alloc原理的时候就探索过isa的方法initIsa,探索流程如下:objc_alloc-->callAlloc-->_objc_rootAllocWithZone-->_class_createInstanceFromZone-->initIsa,这里有一extra_rc来存储引用计数:
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 

    isa_t newisa(0);

    if (!nonpointer) {
        // 非nonpointer
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        // 创建isa,引用计数赋值为1
        newisa.extra_rc = 1;
    }

    isa = newisa;
}

存储引用计数有下面这几种形式:

1.非nonpointerIsa的直接存在sidetable中
2.是nonpointerIsa的如果在extra_ac中能存下,就存在这里
3.如果extra_ac中存不下,就将一半放到sidetable中,并将has_sidetable_rc标志位置为true

 

3.1 nonpointerIsa(开启指针优化的指针)
1.现在大部分指针都是nonpointerIsa
2.非纯指针类型的 isa,isa 中包含了 类信息、对象的引⽤计数 等

3.2 retain和release的原理

3.2.1 retain

(1)在objc的源码中找objc_retain

__attribute__((aligned(16), flatten, noinline))
objc_retain(id obj)
{
    // 如果是 tagged pointer 就直接返回对象,也就是说无需引用计数管理
    if (obj->isTaggedPointerOrNil()) return obj;
    return obj->retain();
}

(2) 查看objc_retain中的rootRetain的源码

ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_retain()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                return swiftRetain.load(memory_order_relaxed)((id)this);
            }
            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
        }
    }

    if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return (id)this;
        }
    }

    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        // 对象正在被释放则直接返回
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        uintptr_t carry;
        
        // 是nonpointerIsa,且 extra_rc 能容纳
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // 是nonpointerIsa,但 extra_rc 容纳不下
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                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;
            // extra_rc改为只存一半引用计数
            newisa.extra_rc = RC_HALF;
            // 一半存到 sidetable 中,要打上标记
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (variant == RRVariant::Full) {
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            // sidetable中存入剩余一半引用计数
            sidetable_addExtraRC_nolock(RC_HALF);
        }

        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}

3.2.1 release

(1)在objc的源码中找objc_release

__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    // 如果是 tagged pointer 就不做任何操作,无需引用计数管理
    if (obj->isTaggedPointerOrNil()) return;
    return obj->release();
}

(2)release流程与retain相反,无需赘述,需要注意的是当引用计数减为0时,会向对象发送dealloc消息

ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
    ......

deallocate:
    // Really deallocate.
    ASSERT(newisa.isDeallocating());
    ASSERT(isa.isDeallocating());

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

3.3 dealloc

(1)在oc的源码搜索_objc_rootDealloc

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    // 是nonpointer 并且同时满足以下条件时才会free
    if (fastpath(isa.nonpointer                     &&
                 // 没有弱引用
                 !isa.weakly_referenced             &&
                 // 没有关联对象
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 // 没有C++的析构函数
                 !isa.has_cxx_dtor                  &&

#else
                 !isa.getClass(false)->hasCxxDtor() &&

#endif
                 // 没有 sidetable 借位
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        // 释放对象
        free(this);
    } 
    else {
        // 调用C++析构函数、删除关联对象、清空散列表中引用计数信息、清空弱引用表中相关数据
        object_dispose((id)this);
    }
}

(2)当对象为nonpointerIsa,并且没有弱引用、没有关联对象,没有C++析构函数、没有sidetable错位时才对对象进行释放,否则先将这些内容抹掉再free对象。



这篇关于iOS-内存管理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程