iOS内存管理笔记

这里只是我对iOS内存管理方面的一些关键知识点的在线笔记,帮忙记忆,未对任何知识点进行深入的分析和探究。

TaggedPointer

这是苹果为了优化内存,对于一些NSString、NSNumber、NSDate等类型的对象进行了优化。在一个8字节的指针里面即存储对象类型,也直接存储其数据值,并且TaggedPointer类型的对象直接存储在常量区中。另外ios10之后苹果底层对taggedPointer的指针地址信息进行了混淆,所以如果直接在lldb下打印时看到真实的信息,需要异或tagPointer的maskt值都能得到真实的内存地址。

NSString *str1 = [NSString stringWithFormat:@"a"];
NSString *str2 = [NSString stringWithFormat:@"b"];
    
NSLog(@"%p-%@",str1,str1);
NSLog(@"%p-%@",str2,str2);
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));

uintptr_t
_objc_decodeTaggedPointer_(id ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}    
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK

#define _OBJC_TAG_MASK (1UL<<63)

2020-11-26 22:39:59.225938+0800 002---taggedPointer[51257:7382935] 0xad65eafc838e1526-a
2020-11-26 22:39:59.938400+0800 002---taggedPointer[51257:7382935] 0xad65eafc838e1516-b
2020-11-26 22:40:00.493712+0800 002---taggedPointer[51257:7382935] 0xa000000000000621

retain操作

当向一个对象发送retain消息时,其内部有以下关键逻辑:
1.先判断对象是否是nonpointer(进行过指针优化的,即把8字节指针64位分成不同的区域,用作不同的功能使用,例如拿出其中的8位来存储引用计数的值等)
2.如果不是nonpointer(未进行指针优化的,一个8字节指针就是纯的内存地址)那么将直接操作全局的SideTable,找到其对应的对象进行加1操作,但每次都操作SideTable都需要开解锁,性能有点低
3.考虑多线程情况,还要判断是否正在释放,如果正在释放中,那根本不需要retain了,直接返回即可
4.到此步骤了,肯定是nonpointer的,这时先对extra_rc进行加1操作,但是extra_rc只占8字节中的8位而已,有可能存储满,所以当这个值存储满足就把满extra_rc的一半存储在sideTable中,这样下次再需要进行+1操作的时候可以继续操作extra_rc,不需要读取sideTable并开锁了,提升了性能。

struct SideTable {
    spinlock_t slock; //访问sideTable需要开解锁
    RefcountMap refcnts;//引用计数表
    weak_table_t weak_table;//弱引用表
}

release操作

当向一个对象发送release消息时,其内部有以下关键逻辑:
1.先判断对象是否是nonpointer,
2.如果不是nonpointer,那么也是直接操作sideTable,进行减一操作,然后直接返回
3.如果是nonpointer,先对extra_rc减1,如果减完了,那么看一下SideTable里面有没有之前存进来,有的话就拿出来放到extra_rc中,所以SideTable中的值只相当于备用存储用。经过此时的减1操作后,如果发现引用计数为0了,直接会向对象发送dealloc消息,进入释放流程。

dealloc流程

如果对象的deallc方法被调用了,那么需要判断它是否有弱引用关系、关联对象、c的析构、引用计数是否在sideTable也有值。如果都没有直接free当前对象即可,否则需要把清空弱引用表、关联对象表、调用c的析构、把对象从sideTable中移除。

inline void
objc_object::rootDealloc()
{
    // free()
    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);
    }
}
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;
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this); 
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

弱引用

SideTable中有一张弱引用表weakTable_t,然后该表中存储了所有对象与其弱引用指向的关系。当对象释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

   struct weak_table_t {
    // 保存了所有指向指定对象的 weak 指针
    weak_entry_t *weak_entries;
    // 存储空间
    size_t    num_entries;
    // 参与判断引用计数辅助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};

typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
    DisguisedPtrobjc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;//二维 objc_object 
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    }
}

自动释放池

自动释放池有几个关键的知识点:
1.objc_autoreleasePoolPush、objc_autoreleasePoolPop 被@autoreleasepool包裹的代码块其实是被这两个方法包裹着。
2.objc_autoreleasePoolPush调用会往page表中压入一个哨兵对象,objc_autoreleasePoolPop调用会向遍历所有pages以及每个page中所有对象,向它们发送release消息。
3.AutoreleasePoolPage对象其实是一个双向链表,并且内部维护了一个存储对象的数组,这个数组有大小限制,当达到4k后开新的page出来,然后存储到新的page中。
4.autoreleasepool只会对应一个线程,每个线程可能会对应多个autoreleasepool,比如autoreleasepool嵌套的情况。

参考资料:
weak的实现原理
OC高级-autoreleasepool的实现原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 从这篇文章开始探索iOS的内存管理,主要涉及的内容有1. 内存布局;2. 内存管理方案:Tagged Pointe...
    风紧扯呼阅读 5,473评论 1 16
  • 本文中的源代码来源:需要下载Runtime的源码,官方的工程需要经过大量调试才能使用。这里有处理好的objc4-7...
    shen888阅读 4,328评论 0 3
  • 1. 对象与类 1.1 对象 对象(Class或id)内部只有一个isa_t联合体指针。isa_t联合体内部只有两...
    我才是臭吉吉阅读 4,068评论 0 2
  • 内存概述 内存是用来存啥的? 内存布局 哈希表 垃圾回收(GC) IOS内存管理机制 MRC & ARC T...
    zhiziZ阅读 4,338评论 0 4
  • 一.内存布局 如上图,内存布局共分为如下几个区: 内核:由系统控制处理的,大概有占有1个GB 栈:函数、方法、局部...
    king_jensen阅读 4,310评论 0 0