美文网首页
内存管理(上)

内存管理(上)

作者: 浅墨入画 | 来源:发表于2021-10-23 18:29 被阅读0次

    内存五大区

    内存布局

    当程序运行时,系统会开辟三个区,分别是:内核区、程序使用的内存五大区保留区
    操作系统分为两种运行级别,分别是内核态用户态。以4GB手机为例,系统将其中的3GB给了五大区+保留区,剩余的1GB内核区使用,如下所示

    image.png
    • 内核区:系统用来进行内核处理操作的区域,主要是系统内核间的消息处理。
    • 五大区:内存五大区
    1. 栈区:存储函数方法,内存地址一般以0x7开头
    2. 堆区:存储通过alloc分配的对象、block copy等,内存地址一般以0x6开头
    3. BSS段:未初始化的全局变量静态变量,内存地址一般以0x1开头
    4. 数据段: 初始化的全局变量静态变量,内存地址一般以0x1开头
    5. text段:程序代码,加载到内存中
    • 保留区:预留给系统处理nil

    这里有个疑问,为什么五大区的最后内存地址是从0x00400000开始的?其主要原因是0x00000000表示nil,不能直接用nil表示一个段,所以单独给了一段内存用于处理nil等情况。

    面试题
    • 通过static修饰的成员变量不占内存,这句话怎么理解?
      static修饰的成员变量全局区,而结构体分配的内存是在堆区,所谓的不占内存是不占用申请的堆区内存空间,而占用的是全局区的内存空间。

    • 栈区的内存是如何定位的?
      通过sp寄存器定位,sp为栈顶。

    内存管理方案

    iOS中内存管理方法,大致可以分为手动管理MRC和自动管理ARC

    MRC
    对象通过引用计数判断是否销毁,需要手动调用对象的retainreleaseautorelease等方法,维护对象引用计数

    • 对象被创建时,引用计数为1
    • 调用对象的retain方法,引用计数+1
    • 调用对象的release方法,引用计数-1
    • autorelease是一个特殊的release,有用延后释放。调用对象的autorelease方法,对象会加入到自动释放池中,最迟会在主循环结束前释放,依赖于Runloop
    • 当对象引用计数为0,系统将销毁此对象。

    ARC
    ARC为自动引用计数管理,属于编译器的一种特性,在WWDC2011iOS5时代被引入

    • 引用计数的规则和MRC手动管理一致
    • 无需手动调用retainreleaseautorelease等方法维护对象引用计数
    • 编译器会在适当的地方自动插入retainrelease方法。

    除了上述的ARCMRC,内存管理方法中还包括几个重要的内容:

    • Tagged Pointer:专门用于存储小的对象,例如:NSNumberNSIndexPathNSDateNSString
    • NonpointerISA:非纯指针类型的isa,isa中包含了类信息对象的引⽤计数
    • SideTables散列表,主要包含引用计数表弱引用表

    WWDC看TaggedPointer

    按照苹果官方的说法,即使我们的app不做任何优化运行速度也会变快,那么苹果底层做了什么优化来作为支撑呢? 今天,我们就一探究竟。

    苹果WWDC2020关于 Objective-C 运行时做出的更改

    Tagged Pointer Format Changes

    什么是Tagged Pointer?
    NSString为例,读取一个常规的NSString,通过栈区存储的指针地址找到堆区空间,然后从堆区读取到字符串的值,整个读取流程效率较低。所以,系统对其进行优化,如果NSString存储的字符串长度较短,会使用Tagged Pointer存储。

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

    Tagged Pointer的读取,只需要将指针解码,通过tagget标记按不同类型规则进行读取即可,这样即节省内存空间,同时提升读取效率

    测试NSString的内存管理

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSString *str1 = @"kc";
        NSLog(@"%p-%@-%@",str1,str1,str1.class);
        
        NSString *str2 = [NSString stringWithString:@"kc"];
        NSLog(@"%p-%@-%@",str2,str2,str2.class);
        
        NSString *str3 = [NSString stringWithFormat:@"kc"];
        NSLog(@"%p-%@-%@",str3,str3,str3.class);
    
        NSString *str4 = [NSString stringWithFormat:@"1234567890"];
        NSLog(@"%p-%@-%@",str4,str4,str4.class);
    }
    
    // 控制台打印
    0x10e516060-kc-__NSCFConstantString
    0x10e516060-kc-__NSCFConstantString
    0xa0000000000636b2-kc-NSTaggedPointerString
    0x6000027c8fe0-1234567890-__NSCFString
    

    对打印结果进行总结,NSString的内存管理主要分为3种

    • __NSCFConstantString:字符串常量,是一种编译时常量retainCount值很大,对其操作不会引起引用计数变化,存储在字符串常量区
    • __NSCFString:是在运行时创建的NSString子类,创建后引用计数会加1,存储在堆上
    • NSTaggedPointerString标签指针是苹果在64位环境下对NSStringNSNumber等对象做的优化。

    对于NSString对象来说

    • 当字符串是由数字英文字母组合且长度小于等于9时,会自动成为NSTaggedPointerString类型,存储在常量区
    • 当有中文或者其他特殊符号时,会直接成为__NSCFString类型,存储在堆区

    x86-64(模拟器64位)下的TaggedPointer

    • objc4-818.2源码中,找到Tagged Pointer的解码方法。进入_objc_decodeTaggedPointer函数
    static inline uintptr_t
    _objc_decodeTaggedPointer(const void * _Nullable ptr)
    {
        // 调用_objc_decodeTaggedPointer_noPermute函数->返回指针
        uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
    #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;
    }
    
    • 查看_objc_decodeTaggedPointer_noPermute函数
    static inline uintptr_t
    _objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
    {
        uintptr_t value = (uintptr_t)ptr;
    
    // 判断OBJC_SPLIT_TAGGED_POINTERS,符合条件进行位运算
    #if OBJC_SPLIT_TAGGED_POINTERS
        if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
            return value;
    #endif
        // 和objc_debug_taggedpointer_obfuscator进行按位异或
        // objc_debug_taggedpointer_obfuscator 进行混淆,可以理解为随机数
        return value ^ objc_debug_taggedpointer_obfuscator;
    }
    
    • objc_debug_taggedpointer_obfuscator的赋值:在dyld读取image时调用_read_images函数,里面包含对initializeTaggedPointerObfuscator函数的调用,对Tagged Pointer进行初始化。
    • 系统生成的Tagged Pointer是编码后的,我们要想了解它的结构,需要对其进行解码
    • objc_debug_taggedpointer_obfuscator全局静态变量,我们可以在程序中使用extern修饰将其导出,自己实现一个解码函数,使用相同的值,将指针再次按位异或即可还原。
    extern uintptr_t objc_debug_taggedpointer_obfuscator; 
    
    uintptr_t 
    kc_objc_decodeTaggedPointer(id ptr) { 
        return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
    }
    

    案例测试代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSString *str = [NSString stringWithFormat:@"kc"];
        NSLog(@"%p-%@-%@ - 0x%lx",str,str,str.class,kc_objc_decodeTaggedPointer(str)); 
    }
    
    // 控制台打印
    0xa0000000000636b2-kc-NSTaggedPointerString - 0xa0000000000636b2
    
    • lldb使用p/t命令,查看二进制形式
    (lldb) p/t 0xa0000000000636b2
    // 0b表示二进制; 高地址第一位1表示该isa为Tagged Pointer类型
    (unsigned long) $0 = 0b1010000000000000000000000000000000000000000001100011011010110010
    
    • 通过位运算,获取有效负载
    (lldb) p/t $0 >> 4 
    (unsigned long) $1 = 0b0000101000000000000000000000000000000000000000000110001101101011
    
    // 低地址最后16位,每8位进行一次打印
    (lldb) po 0b01101011
    107
    (lldb) po 0b01100011
    99
    
    // 里面存储的内容,其实就是字符的assic码
    (lldb) po (char)107 
    'k' 
    (lldb) po (char)99
    'c'
    
    • isa转为二进制,高地址的前4位0b1010,第一位表示该isaTagged Pointer类型,后面三位010表示Tagged Pointer所存储的类型
    // 2表示上面str是字符串类型
    (lldb) po 0b010 
    2
    
    • 对应objc源码中的类型,上一步打印的2表示str是字符串类型
    // 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, 
    ......
    

    NSNumber类型自己尝试......

    arm64(真机64位)下的TaggedPointer

    objc4-818.2源码中,查看Tagged Pointer在真机上的编码方法

    static inline void * _Nonnull
    _objc_encodeTaggedPointer(uintptr_t ptr)
    {
        uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
    #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;
    }
    
    • 实现真机环境编码解码函数
    #define kc_OBJC_TAG_INDEX_MASK 0x7UL
    #define kc_OBJC_TAG_INDEX_SHIFT 0
    
    extern uint8_t objc_debug_tag60_permutations[8];
    uintptr_t kc_objc_obfuscatedTagToBasicTag(uintptr_t tag) {
        for (unsigned i = 0; i < 7; i++)
            if (objc_debug_tag60_permutations[i] == tag)
                return i;
        return 7;
    }
    
    uintptr_t 
    kc_objc_decodeTaggedPointer(id ptr) {
        uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
        uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK; 
        
        value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT); 
        value |= kc_objc_obfuscatedTagToBasicTag(basicTag) << kc_OBJC_TAG_INDEX_SHIFT; 
        
        return value; 
    }
    
    static inline uintptr_t kc_objc_basicTagToObfuscatedTag(uintptr_t tag) { 
        return objc_debug_tag60_permutations[tag]; 
    }
    
    void * kc_objc_encodeTaggedPointer(uintptr_t ptr) {
        uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr); 
        
        uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK; 
        uintptr_t permutedTag = kc_objc_basicTagToObfuscatedTag(basicTag); 
        value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT); 
        value |= permutedTag << kc_OBJC_TAG_INDEX_SHIFT;
        
        return (void *)value; 
    }
    
    • 继续上面字符串案例,使用真机运行
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSString *str = [NSString stringWithFormat:@"kc"];
        NSLog(@"%p-%@-%@ - 0x%lx",str,str,str.class,kc_objc_decodeTaggedPointer(str));
    }
    
    // 控制台打印
    0x800000000031b592-kc-NSTaggedPointerString - 0x800000000031b592
    
    // lldb使用p/t命令,查看二进制形式
    (lldb) p/t 0x800000000031b592 
    // 和模拟器的区别,高地址第一位依然表示该isa为Tagged Pointer类型;
    // 但类型的位置发生变化,存储在低地址最后三位,即010
    (unsigned long) $0 = 0b1000000000000000000000000000000000000000001100011011010110010010
    
    // 打印低地址最后三位,查看类型
    (lldb) po 0b010 
    2  //表示字符串类型
    
    // 010类型前面4位0010,即低地址4~7位,表示字符串长度,查看长度
    (lldb) po 0b0010 
    2  //表示字符串长度
    
    • 添加NSNumber类型
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSNumber *number = @6;
        NSLog(@"%p-%@-%@ - 0x%lx",number,number,number.class,kc_objc_decodeTaggedPointer(number));
        
        NSNumber *number1 = @(6.0);
        NSLog(@"%p-%@-%@ - 0x%lx",number1,number1,number1.class,kc_objc_decodeTaggedPointer(number1));
    }
    
    // 控制台打印
    0x8000000000000313-6 -__NSCFNumber- 0x8000000000000313 
    0x800000000000032b-6 -__NSCFNumber- 0x800000000000032b
    
    // lldb使用p/t命令,查看二进制形式
    (lldb) p/t 0x8000000000000313 
    // 低地址最后三位011表示NSNumber的类型
    // 低地址4~7位,分别存储0010和0101,表示存储的基本数据类型。
    // 例如:int、long、double、float等。
    (unsigned long) $0 = 0b1000000000000000000000000000000000000000000000000000001100010011 
    (lldb) p/t 0x800000000000032b
    (unsigned long) $1 = 0b1000000000000000000000000000000000000000000000000000001100101011
    
    // 打印低地址4~7位,查看NSNumber数据类型
    (lldb) po 0b0010
    2  //表示int型
    (lldb) po 0b0101
    5  //表示double型
    

    NSNumber枚举值在objc源码中未定义,只能通过代码测试对应的类型:char:0short:1int:2long:3float:4double:5

    NSIndexPathNSDate类型自己尝试......

    关闭混淆

    真机环境探索结构需要我们自己实现编码解码的代码,其实这里还有更简单的方式。

    • 通过源码发现,初始化时如果不符合混淆条件,objc_debug_taggedpointer_obfuscator会被设置为0
    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.
            // 不符合混淆条件,置为0
            objc_debug_taggedpointer_obfuscator = 0;
        }
    }
    
    • 查看DisableTaggedPointerObfuscation混淆,它的值取决于OBJC_DISABLE_TAG_OBFUSCATION环境变量的设置
    OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION,    "disable obfuscation of tagged pointers")
    
    • 测试项目中添加OBJC_DISABLE_TAG_OBFUSCATION环境变量,将其设置为YES。即关闭混淆
    image.png
    • 运行工程,查看控制台打印。直接获得未编码的isa
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSNumber *number = @6;
        NSLog(@"%p-%@-%@ - 0x%lx",number,number,number.class,(uintptr_t)number);
        
        NSNumber *number1 = @(6.0);
        NSLog(@"%p-%@-%@ - 0x%lx",number1,number1,number1.class,(uintptr_)number1);
    }
    
    // 控制台打印
    0x8000000000000313-6 -__NSCFNumber- 0x8000000000000313
    0x800000000000032b-6 -__NSCFNumber- 0x800000000000032b
    

    TaggedPointer面试题

    下面代码运行结果如何?是否会产生崩溃?

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self taggedPointerDemo];
    }
    
    - (void)taggedPointerDemo { 
        self.queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT); 
        
        for (int i = 0; i<10000; i++) { 
            dispatch_async(self.queue, ^{ 
                self.nameStr = [NSString stringWithFormat:@"kc"]; 
                NSLog(@"%@",self.nameStr); 
            });
        } 
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 
        for (int i = 0; i<100000; i++) { 
            dispatch_async(self.queue, ^{ 
                self.nameStr = [NSString stringWithFormat:@"kc_和谐学习不急不躁"];
                NSLog(@"%@",self.nameStr); 
            }); 
        } 
    }
    

    taggedPointerDemo的运行,不会出现任何问题。因为字符串kc会被优化成NSTaggedPointerString类型。

    touchesBegan的运行,会导致程序崩溃。因为字符串中包含中文,所以使用__NSCFString类型。它的值存储在堆区,赋值的过程本质上对旧值release对新值retain。当多线程执行时,可能出现多次release造成过度释放,一些野指针的操作导致程序崩溃。

    • objc源码中查找rootRetain函数
    image.png

    如果是TaggedPointerisa,直接返回。不用进行后面的新旧值retainrelease

    • 查看rootRelease函数
    image.png

    判断如果是TaggedPointerisa,直接返回false

    Tagged Pointer 小结
    • Tagged Pointer专门用于存储小的对象。例如NSNumberNSDateNSStringNSIndexPath
    • 指针由标志+值+扩展+类型组成,通过混淆编码成指针地址;
    • 使用Tagged Pointer类型的好处,节省内存空间,提升读取效率;
    • Tagged Pointer触发retainrelease直接返回,这意味着它不需要ARC进行管理,而是直接被系统回收释放;
    • Tagged Pointer的内存并不存储在堆中,而是在常量区中,也不需要mallocfree,所以可以直接读取,相比存储在堆区的数据读取,效率上快了3倍左右。创建的效率相比堆区快了近100倍左右
    • 综合来说taggedPointer的内存管理方案,比常规的内存管理,要快很多。
      Tagged Pointer的64位地址中,前4位代表类型,后4位主要适用于系统做一些处理,中间56位用于存储值

    优化内存建议:对于NSString来说,当字符串较小时,建议直接通过@""初始化,因为存储在常量区,可以直接进行读取,会比WithFormat初始化方式更加快速。

    retain&release

    nonpointer:表示是否对isa指针开启指针优化
    0:纯isa指针,
    1:不止是类对象地址。isa 中包含了类信息对象的引用计数等。

    Tagged Pointer类似, NONPOINTER_ISA也是对isa存储位的优化处理,以使其64位能更充分的得到利用,保存相关数据而不是空着浪费掉。
    其中shiftclsPalLoad相似,用来承载有效数据。

    对象的引用计数存储在isa中的extra_rc
    extra_rc表示该对象的引⽤计数值,实际上是引⽤计数值-1。例如,如果对象的引⽤计数为10,那么extra_rc为9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的has_sidetable_rc
    has_sidetable_rc当对象引⽤计数⼤于10时,则借⽤该变量存储进位。此时会配合散列表SideTables进行存储。

    • objc源码中查看rootRetain函数
    ALWAYS_INLINE id 
    objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) {
    
        //1、判断如果是TaggedPointer,什么都不处理,直接返回 
        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 
        //2、如果是纯isa,判断如果是一个类,也不需要Retain操作 
            if (oldisa.getDecodedClass(false)->isMetaClass()) { 
                ClearExclusive(&isa.bits); 
                return (id)this; 
            } 
        }
        
        do {
            transcribeToSideTable = false; 
            newisa = oldisa; 
            if (slowpath(!newisa.nonpointer)) { 
                //3、如果是纯isa,使用散列表,进行Retain操作 
                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())) {
                //4、如果当前isa正在释放,不需要Retain操作
                ClearExclusive(&isa.bits);
                if (sideTableLocked) { 
                    ASSERT(variant == RRVariant::Full); 
                    sidetable_unlock();
                } 
                
                if (slowpath(tryRetain)) {
                    return nil; 
                } else { 
                    return (id)this; 
                } 
            }
            
            //5、通过bits对RC_ONE进行Retain操作,引用计数+1,将状态赋值
            carryuintptr_t carry;
            newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // 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.
                
                if (!tryRetain && !sideTableLocked) sidetable_lock();
                
                //6、存储已满,修改一些标记,设置isa的extra_rc和has_sidetable_rc
                //RC_HALF表示砍半,将一半存储在
                extra_rcsideTableLocked = true;
                transcribeToSideTable = true;
                newisa.extra_rc = RC_HALF;
                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. 
                //7、将另一半存储到散列表SideTable中 
                sidetable_addExtraRC_nolock(RC_HALF); 
            }
            
            if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
        } else {
            ASSERT(!transcribeToSideTable); ASSERT(!sideTableLocked); 
        } 
    
        return (id)this;
    }
    

    retain流程图

    image.png
    • 查看rootRelease函数
    ALWAYS_INLINE bool 
    objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant) {
    
        //1、判断如果是TaggedPointer,什么都不处理,直接返回 
        if (slowpath(isTaggedPointer())) return false;
        
        bool sideTableLocked = false; 
        
        isa_t newisa, oldisa;
        
        oldisa = LoadExclusive(&isa.bits);
        
        if (variant == RRVariant::FastOrMsgSend) { 
            // These checks are only meaningful for objc_release()
            // 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()) { 
                    swiftRelease.load(memory_order_relaxed)((id)this); 
                    return true; 
                } 
                
                ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release)); 
                return true; 
            } 
        }
        
        if (slowpath(!oldisa.nonpointer)) { 
            // a Class is a Class forever, so we can perform this check once 
            // outside of the CAS loop 
            //2、如果是纯isa,判断如果是一个类,也不需要Release操作 
            if (oldisa.getDecodedClass(false)->isMetaClass()) { 
                ClearExclusive(&isa.bits); 
                return false; 
            } 
        }
        
    retry:
        do {
            newisa = oldisa;
            
            if (slowpath(!newisa.nonpointer)) {
                //3、如果是纯isa,使用散列表,进行Release操作
                    ClearExclusive(&isa.bits);
                    return sidetable_release(sideTableLocked, performDealloc);
            }
            
            if (slowpath(newisa.isDeallocating())) {
                //4、如果当前isa正在释放,不需要Release操作
                ClearExclusive(&isa.bits);
                if (sideTableLocked) {
                    ASSERT(variant == RRVariant::Full);
                    sidetable_unlock();
                }
                return false;
            }
            
            // don't check newisa.fast_rr; we already called any RR overrides
            //5、通过bits对RC_ONE进行Release操作,引用计数-1,将状态赋值
            carryuintptr_t carry;
            newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
            
            if (slowpath(carry)) {
                // don't ClearExclusive()
                //6、如果extra_rc减空,进入underflow代码流程
                goto underflow;
            }
        } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
        
        if (slowpath(newisa.isDeallocating()))
            goto deallocate; 
        
        if (variant == RRVariant::Full) {
            if (slowpath(sideTableLocked)) sidetable_unlock();
         } else {
             ASSERT(!sideTableLocked); 
         }
         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)) {
        
            //7、判断当前已使用散列表存储 
            if (variant != RRVariant::Full) {
                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. 
                oldisa = LoadExclusive(&isa.bits); 
                goto retry; 
            }
            
            // Try to remove some retain counts from the side table.
            //从散列表中取出一半
            auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
            
            //如果散列表中取空了,标记emptySideTable
            bool emptySideTable = borrow.remaining == 0;  // we'll clear the side table if no refcounts remain there
            
            //判断从散列表中取出内容
            if (borrow.borrowed > 0) {
            
                // Side table retain count decreased.
                // Try to add them to the inline count.
                
                bool didTransitionToDeallocating = false;
                
                
                //进行-1操作,赋值extra_rc
                //通过emptySideTable标记,修改has_sidetable_rc
                newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too
                newisa.has_sidetable_rc = !emptySideTable;
                
                //存储到isa的bits中
                bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
                
                if (!stored && oldisa.nonpointer) { 
                    // 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.
                    //存储失败的补救处理 
                    uintptr_t overflow; 
                    newisa.bits = addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
                    newisa.has_sidetable_rc = !emptySideTable; 
                    
                    if (!overflow) { 
                        stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits); 
                        if (stored) {
                            didTransitionToDeallocating = newisa.isDeallocating();
                        } 
                    } 
                }
                
                if (!stored) {
                    // Inline update failed.
                    // Put the retains back in the side table.
                    ClearExclusive(&isa.bits);
                    sidetable_addExtraRC_nolock(borrow.borrowed);
                    oldisa = LoadExclusive(&isa.bits);
                    goto retry;
                }
                
                // Decrement successful after borrowing from side table. 
                if (emptySideTable)
                    sidetable_clearExtraRC_nolock(); 
                    
                if (!didTransitionToDeallocating) { 
                    if (slowpath(sideTableLocked)) sidetable_unlock();
                    return false; 
                }
            } else {
                // Side table is empty after all. Fall-through to the dealloc path. 
            }
        }
        
    deallocate: 
        // Really deallocate.
        //8、进入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;       
    }
    

    相关文章

      网友评论

          本文标题:内存管理(上)

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