内存管理(上)

内存五大区

内存布局

当程序运行时,系统会开辟三个区,分别是:内核区、程序使用的内存五大区保留区
操作系统分为两种运行级别,分别是内核态用户态。以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 *)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;       
}

你可能感兴趣的:(内存管理(上))