OC_内存管理

236 阅读6分钟

内存五大区:

  • 栈(Stack):
    • 先进后出
    • 系统自动管理
    • 内存按顺序分配,通常存储局部变量、方法参数、对象指针。
    • 存储空间有限:主线程1M,子线程512k
    • 一般以0x7开头
  • 堆(Heap):
    • 手动管理
    • 内存随机分配
    • 通常存储着通过allocnewmalloc创建的变量,C下需要调用free函数进行释放
    • 一般以0x6开头
  • 静态(Static)/全局区(Global):
    • 存储全局变量、静态变量
  • 常量区(Constant):
    • 存储着一些常量
    • 一般以0x1开头
  • 代码区:
    • 存储编译生成的二进制代码

数据类型

  • 值类型
    • 存储在栈上
    • 无需对其进行内存管理
    • 地址中存储的是值
  • 引用类型
    • 存储在堆上
    • 需要对其进行内存管理
    • 地址中存储的是堆区地址

iOS内存管理方案:

通常系统在对简单数据进行存储时,需要通过栈区存储的指针地址,找到堆区空间,再从堆区读取到,整个读取流程效率较低,占用空间。所以,系统对其进行优化,将其标记为 Tagged Pointer,其值存储在栈区加快读取效率,减少占用空间

tagged point:是一个指针,指针中包含tagged标记,针对小对象类型(长度小于9),在地址的最高位打上标记1,这个时候中存储的不再是地址,而是一个真正的,而且在进程初始化时会与一个随机值进行混淆

NSString *firstString = @"helloworld";
NSString *secondString = [NSString stringWithFormat:@"helloworld"];
NSString *thirdString = @"hello";
NSString *fourthSting = [NSString stringWithFormat:@"hello"];

NSLog(@"%p %@",firstString,[firstString class]);
NSLog(@"%p %@",secondString,[secondString class]);
NSLog(@"%p %@",thirdString,[thirdString class]);
NSLog(@"%p %@",fourthSting,[fourthSting class]);   

//输出
//0x101a32070 __NSCFConstantString 字符串常量,是一种 编译时常量,retainCount 值很大,对其操作不会引起引用计数变化,存储在`常量区`
//0x600003838e00 __NSCFString 运行时创建的 NSString子类,创建后引用计数默认为1,存储在`堆区`
//0x101a32090 __NSCFConstantString
//0xa08d84b039e90fdc NSTaggedPointerString 字符串优化类型,存储在`常量区`

tagged point 编码

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    //与 随机值(objc_debug_taggedpointer_obfuscator) 进行 异或 混淆
    uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
//是否是ARM64
#if OBJC_SPLIT_TAGGED_POINTERS
    if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
        return (void *)ptr;
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
    value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
    value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
    return (void *)value;
}

随机数是在dyld读取image_read_images方法中调用 initializeTaggedPointerObfuscator() 方法进行的生成

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { 
    ...... 
    if (DisableTaggedPointers) { 
        disableTaggedPointers(); 
    } 
    // 进程启动时初始化一个随机的值,用于混淆tagged pointer指针 
    initializeTaggedPointerObfuscator(); 
    ...... 
}

static void
initializeTaggedPointerObfuscator(void)
{
    if (!DisableTaggedPointerObfuscation) {
        // 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;
        
#if OBJC_SPLIT_TAGGED_POINTERS
        // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
        objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
        // Shuffle the first seven entries of the tag permutator.
        int max = 7;
        for (int i = max - 1; i >= 0; i--) {
            int target = arc4random_uniform(i + 1);
            swap(objc_debug_tag60_permutations[i],
                 objc_debug_tag60_permutations[target]);
        }
#endif
    } else {
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation.
        objc_debug_taggedpointer_obfuscator = 0;
    }
}

tagged point 解码

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
//ARM64 会进入 if
#if OBJC_SPLIT_TAGGED_POINTERS
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
    value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
    return value;
}

对上面 0xa08d84b039e90fdc 进行解码后,得到 0xa00006f6c6c65685,对其 p/t 输出之后得到地址里的内容 image.png

基于 inter处理器 地址内容分解

image.png inter下头部接下来3位表示标签位ARM64则是最底3位表示标签位,表示当前值的类型
inter下最低4位来表示字符长度ARM64标签位向前4位表示字符长度

//tagged 字符类型
// 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,
    
// 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7,

ARC:自动内存管理,编译器在编译时会自动为我们在合适的地方插入 retain,release,autorelease 方法,进行内存管理

引用计数

管理对象生命周期的一种方式,当其 ==0 时,该对象就会被释放掉,同时释放的还是有对象占用的内存空间

引用计数的存储

在一个对象alloc的时候,会创建其isa指针,其创建isa的方法initIsa中,里边就存在记录对象引用计数的参数:extrc_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 {
        ...
        // 创建isa,引用计数赋值为1 
        newisa.extra_rc = 1; 
    } 
    isa = newisa; 
}
  • 引用计数存储情况:
    • nonpointerIsa,直接存在sidetable
    • nonpointerIsa
      • 是否正在被释放,是就返回
      • 若不是,就存储在extra_rc
      • extra_rc存不下,就借位sidetable进行存储

retain 原理

__attribute__((aligned(16), flatten, noinline))
objc_retain(id obj) { 
    // 如果是 tagged pointer 直接返回
    if (obj->isTaggedPointerOrNil())  return obj; 
    return obj->retain(); }
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;
        
        //不是nonpointerisa,引用计数存储在sideTable中
        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;
            }
        }
        
        //nonpointerisa下,直接操作 extra_rc
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        
        //nonpointerisa下,extra_rc 存不下时
        if (slowpath(carry)) {
            // 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.
            // copy 出 extra_rc 的一半,放入到 sidetable 中
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            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;
}
方法流程:
1、判断是否是tagged pointer,是就return
2、获取对象isa,判断其是不是 nonpointisa,不是,引用计数就直接在side table中进行++
3、是 nonpointisa,看其是不是正在被释放,是就return
4、若不是,看extrc_rc能否存的下,能就直接存
5、若不能,extrc_rc 取出一半存储到 side table 中,并将 has_sidetable_rc 标记为 yes

release原理

__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    // 如果是 tagged pointer,就return
    if (obj->isTaggedPointerOrNil()) return;
    return obj->release();
}
ALWAYS_INLINE bool
objc_object::rootRelease(**bool** performDealloc, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return false;
    ...
    
deallocate:

    // Really deallocate.
    ASSERT(newisa.isDeallocating());
    ASSERT(isa.isDeallocating());
    
    if (slowpath(sideTableLocked)) sidetable_unlock();
    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));

    }
    return true;
}
与retain大致相同,方法流程:
1、判断是否是tagged pointer,是就return
2、获取对象isa,判断其是不是 nonpointisa,不是,引用计数就直接在side table中进行--
3、是 nonpointisa,看其是不是正在被释放,是就return
4、若不是,就对 extrc_rc 进行 -- 操作
5、当 extrc_rc == 0 时,判断 has_sidetable_rc 是否为yes
6、是, 取出 side table 中的值 给 extrc_rc,进行 -- 操作,has_sidetable_rc 标记为 no
7、当 引用计数为 0 时,调用 dealloc

dealloc原理

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

    if (fastpath(isa.nonpointer                     && // 是nonpointerisa
                 !isa.weakly_referenced             && // 无弱引用
                 !isa.has_assoc                     && // 无关联地对象
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  && // 无析构函数
#else
                 !isa.getClass(false)->hasCxxDtor() &&
#endif
                 !isa.has_sidetable_rc))               // 无借位
    {
        assert(!sidetable_present());
        //释放
        free(this);
    } 
    else {
        // 调用C++析构函数、删除关联对象、清空散列表中引用计数信息、清空弱引用表中相关数据
        object_dispose((id)this);
    }
}
释放流程:当 isa 是 nonpointerisa,并且 无弱引用、无关联对象、无析构函数、无借位是进行释放。
否则调用 object_dispose() 进行删除关联对象、清空散列表中引用计数信息、清空弱引用表中相关数据 操作