美文网首页
iOS内存管理笔记

iOS内存管理笔记

作者: 会跑的鱼_09 | 来源:发表于2020-11-27 18:25 被阅读0次

    这里只是我对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内存管理笔记

          本文链接:https://www.haomeiwen.com/subject/tpxfwktx.html