likes
comments
collection
share

iOS底层原理探索-----内存管理 (一)

作者站长头像
站长
· 阅读数 26

资源准备

内存布局

当程序运行时,系统会开辟三个区,分别是:内核区程序使用的内存五大区保留区

内核区

操作系统分为两种运行级别,分别是内核态与用户态。当程序运行,系统会分配出4G虚拟内存,其中内核区占用1G,用于进行内核处理操作的区域。剩余3G,会预留给程序使用的内存五大区和保留区。

内存五大区

内存主要分为五个部分,分别是:栈区堆区全局区常量区代码区。如下如:

iOS底层原理探索-----内存管理 (一)

栈区(Stack

  • 栈是系统数据结构,栈所对应的进程或线程是唯一的;

  • ARM64系统下,栈的地址由高到底,向下拉伸;

  • 栈是一块连续的内存区域,遵循先进后出(FILO)原则;

  • 栈区在运行时分配空间,iOS中栈的地址一般以0X7开头;

  • 栈区由系统负责分配和销毁,效率更高,速度更快;

  • 栈区中存储局部变量和方法/函数的参数;

  • 栈区的内存昂贵,大小有限制;

  • iOS的主线程栈大小为1MB,其他线程为512KB

堆区(Heap

  • 堆区的内存空间是不连续的,利于增删,不利于查询

  • 堆区在运行时分配空间,iOS中堆区的地址一般以0X6开头

  • 堆区由开发者负责申请开辟和销毁,效率不如栈

  • 堆区的存储

    • OC中,使用allocnew创建的对象,在ARC模式下,由系统回收释放

    • C语言中,使用malloccallocrealloc开辟的空间,需要手动调用free函数对其释放

  • 堆区的内存空间较大,可以存储整个结构体以及成员变量。而栈区适合存储结构体指针

全局静态区(.bss & .data

  • 全局区在编译时分配内存空间,在iOS中一般以0x1开头;

  • 全局区的数据一直存在,程序结束后由系统释放;

  • 全局区存储全局变量和static修饰的静态变量,其中静态变量分为全局静态变量和静态局部变量;

  • 未初始化的变量存储在BSS区(.bss),已初始化的变量存储在数据区(.data)。

常量区(.rodata

  • 常量区在编译时分配内存空间;

  • 常量区的数据一直存在,程序结束后由系统释放;

  • 常量区存储了程序中定义的死值,例如:int a = 10;

代码区(.text

  • 代码区在编译时分配内存空间;

  • 代码区存储程序运行时的代码,会被编译成二进制存进内存。

保留区

ARM64系统中,内存五大区的低地址从0x00400000开始。其主要原因是0x00000000表示nil,我们不能直接用nil表示一个段,故此系统单独预留一段内存用于处理nil等情况。

面试题

栈区的内存是如何定位的?

通过sp寄存器定位,sp为栈顶。

全局变量和局部变量的区别?

  • 全局变量保存在内存的全局区,bss+data段,占用静态的存储单元。

  • 局部变量保存在栈中,当函数被调用时,才会动态为变量分配存储单元。

Block是否可以直接修改全局变量?

可以直接修改,因为全局变量、全局静态变量,都存储在全局区,它们的作用域更广泛,可以在Block中直接修改;

局部变量被Block捕获,成为Block结构体的成员变量。它会以值的形式传递,而非地址,所以不能直接修改,需要使用__block修饰;

而局部静态变量,可以在Block中直接修改,因为Block中直接捕获的是指针。

内存管理方案

iOS中内存管理方法,分为手动管理MRC和自动管理ARC。下面对这两个来探索下。

MRC

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

  • 对象被创建时,引用计数为1

  • 调用对象的retain方法,引用计数+1

  • 调用对象的release方法,引用计数-1

  • autorelease是一个特殊的release,有用延后释放。调用对象的autorelease方法,对象会加入到自动释放池中,最迟会在主循环结束前释放,依赖于Runloop

  • 当对象引用计数为0,系统将销毁此对象。

ARC

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

  • 引用计数的规则和MRC手动管理一致;

  • 无需手动调用retainreleaseautorelease等方法维护对象引用计数;

  • 编译器会在适当的地方自动插入retainrelease方法。

Tagged Pointer

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

  • Tagged Pointer:专门用于存储小的对象,例如:NSNumberNSIndexPathNSDateNSString

  • NonpointerISA:非纯指针类型的isaisa中包含了类信息、对象的引⽤计数等;

  • SideTables:散列表,主要包含引用计数表和弱引用表。

概述

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

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

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

案例代码:

- (void)viewDidLoad { 
    [super viewDidLoad];
    
    NSString *str = [NSString stringWithFormat:@"kc"];
    NSLog(@"%p-%@-%@",str,str,str.class); 
}

输出内容:

 0x800000000031b592-kc-NSTaggedPointerString
  • 上述案例字符串的所属为NSTaggedPointerString

  • 系统对字符串进行优化,当字符串是由数字、英文字母组合且长度<= 9,会自动成为NSTaggedPointerString类型,存储在常量区。

  • 当字符串中包含中文或者其他特殊符号,会直接成为__NSCFString类型,存储在堆区。

NSString内存管理的三种类型:

  • __NSCFConstantString:字符串常量,是一种编译时常量,retainCount值很大,对其操作不会引起引用计数变化,存储在字符串常量区;

  • __NSCFString:运行时创建的NSString子类,创建后引用计数默认为1,存储在堆区;

  • NSTaggedPointerString:字符串符合优化条件,自动成为NSTaggedPointerString类型,存储在常量区。

结构

模拟器

objc4-818.2源码中,找到Tagged Pointer的解码方法。进入_objc_decodeTaggedPointer函数:

#if __arm64__ 
// ARM64 uses a new tagged pointer scheme where normal tags are in 
// the low bits, extended tags are in the high bits, and half of the 
// extended tag space is reserved for unobfuscated payloads. 
# define OBJC_SPLIT_TAGGED_POINTERS 1 
#else 
# define OBJC_SPLIT_TAGGED_POINTERS 0 
#endif 

static inline uintptr_t 
_objc_decodeTaggedPointer(const void * _Nullable ptr) { 
    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函数,返回指针;

  • 判断OBJC_SPLIT_TAGGED_POINTERS,符合条件进行位运算;

进入_objc_decodeTaggedPointer_noPermute函数:

static inline uintptr_t 
_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr) { 
    uintptr_t value = (uintptr_t)ptr;
#if OBJC_SPLIT_TAGGED_POINTERS 
    if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK) 
        return value; 
#endif 
    return value ^ objc_debug_taggedpointer_obfuscator; 
}
  • objc_debug_taggedpointer_obfuscator进行按位异或;

  • objc_debug_taggedpointer_obfuscator,可以理解为随机数。

objc_debug_taggedpointer_obfuscator的赋值:在dyld读取image时,调用_read_images函数。里面包含对initializeTaggedPointerObfuscator函数的调用,对Tagged Pointer进行初始化。

iOS底层原理探索-----内存管理 (一)

进入initializeTaggedPointerObfuscator函数:

static void 
initializeTaggedPointerObfuscator(void) { 
    // && dyld_program_sdk_at_least(dyld_fall_2018_os_versions) 
    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; 
    } 
}
  • 判断是否开启混淆,开启将objc_debug_taggedpointer_obfuscator赋值随机数,否则赋值为0

系统生成的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
  • 使用模拟器运行程序,解码后的指针地址:0xa0000000000636b2

lldb中,使用p/t命令,查看二进制形式:

(lldb) p/t 0xa0000000000636b2
(unsigned long) $0 = 0b1010000000000000000000000000000000000000000001100011011010110010
  • 0b表示二进制;

  • 高地址第一位1,表示该isaTagged Pointer类型。

通过位运算,获取有效负载:

(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位,第一位表示该isaTagged Pointer类型,后面三位010表示Tagged Pointer所存储的类型:

(lldb) po 0b010 
2

对应objc源码中的类型:

// 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, 

...

真机

Tagged Pointer结构,在真机上的变现形式:

iOS底层原理探索-----内存管理 (一)

实现真机环境解码函数:

#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; 
}

上述案例,使用真机运行:

0x800000000031b592-kc-NSTaggedPointerString - 0x800000000031b592

lldb中,使用p/t命令,查看二进制形式:

(lldb) p/t 0x800000000031b592 
(unsigned long) $0 = 0b1000000000000000000000000000000000000000001100011011010110010010
  • 和模拟器的区别,高地址第一位,没有变化,表示该isaTagged Pointer类型;

  • 但类型的位置发生变化,存储在低地址最后三位。

低地址4~70010,表示字符串长度:

(lldb) po 0b0010 
2

查看NSNumber类型:

number:0x8000000000000313-6 -__NSCFNumber- 0x8000000000000313 
number1:0x800000000000032b-6 -__NSCFNumber- 0x800000000000032b

lldb中,使用p/t命令,查看二进制形式:

(lldb) p/t 0x8000000000000313 
(unsigned long) $0 = 0b1000000000000000000000000000000000000000000000000000001100010011 

(lldb) p/t 0x800000000000032b
(unsigned long) $1 = 0b1000000000000000000000000000000000000000000000000000001100101011
  • 低地址最后三位011表示NSNumber的类型;

  • 低地址4~7位,分别存储00100101,表示存储的基本数据类型。例如:intlongdoublefloat等。

该枚举值在objc源码中未定义,只能通过代码测试类型所对应的枚举值:

  • char0

  • short1

  • int2

  • long3

  • float4

  • double5

关闭混淆

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

通过源码发现,在初始化时,如果不符合混淆条件,objc_debug_taggedpointer_obfuscator会被设置为0

iOS底层原理探索-----内存管理 (一)

搜索DisableTaggedPointerObfuscation

OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers")
  • 它的值取决于OBJC_DISABLE_TAG_OBFUSCATION环境变量的设置。

在测试项目中,添加OBJC_DISABLE_TAG_OBFUSCATION环境变量,将其设置为YES。如下图:

iOS底层原理探索-----内存管理 (一)

测试案例代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSNumber *number = @6;
    NSLog(@"number:%p-%@ -%@- 0x%lx",number,number,number.class,(uintptr_t)number); 
}

打印结果:

number0x8000000000000313-6 -__NSCFNumber- 0x8000000000000313
  • 此时,直接获得未编码的isa

内存管理

案例代码:

- (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造成过度释放,一些野指针的操作导致程序崩溃。

上述案例中,NSTaggedPointerString类型多线程执行不会崩溃,主要在于它的内存管理机制。

objc源码中,找到rootRetain函数:

iOS底层原理探索-----内存管理 (一)

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

进入rootRelease函数:

iOS底层原理探索-----内存管理 (一)

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

Tagged Pointer触发retainrelease,什么都不处理直接返回。这意味着它不需要ARC进行管理,而是直接被系统回收释放。

总结

  • Tagged Pointer专门用于存储小的对象,例如:NSNumberNSIndexPathNSDateNSString

  • 指针由标志+值+扩展+类型组成,通过混淆编码成指针地址;

  • 使用Tagged Pointer类型的好处,节省内存空间,提升读取效率;

  • Tagged Pointer触发retainrelease,什么都不处理直接返回。这意味着它不需要ARC进行管理,而是直接被系统回收释放;

  • Tagged Pointer的内存并不存储在堆中,而是在常量区中,所以不需要mallocfree。它的读取速度,相比存储在堆区的数据读取,效率上快了3倍左右。创建的效率相比堆区快了近100倍左右;

  • Tagged Pointer结构,在ARM64架构下:

    • 高地址第一位,标志位,表示该isaTagged Pointer类型

    • 低地址最后三位,表示存储的类型,例如:NSNumberNSIndexPathNSDateNSString

    • 低地址4~7位,记录扩展信息,例如:存储NSString记录字符串长度、存储NSNumber记录基本数据类型的枚举值;

    • 其余56位,用于存储值。

SideTables

NonpointerISA表示它不⽌是类对象地址,isa中包含了类信息、对象的引⽤计数等。

ARM64架构为例,引⽤计数存储在NonpointerISA位域的extra_rc中,高位的前19位

extra_rc表示该对象的引⽤计数值,实际上是引⽤计数值-1。例如,如果对象的引⽤计数为10,那么extra_rc9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的has_sidetable_rc

has_sidetable_rc当对象引⽤计数⼤于10时,则借⽤该变量存储进位。此时会配合散列表SideTables进行存储。

retain

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流程:

  • 【步骤一】判断如果是TaggedPointer:

    • 是,什么都不处理,直接返回

    • 不是,进入【第二步】

  • 【第二步】判断是纯isa,并且是一个类

    • 是,不需要Retain操作,直接返回

    • 不是,进入【第三步】

  • 【第三步】如果是纯isa,但不是类

    • 是,使用散列表,进行Retain操作

    • 不是,进入【第四步】

  • 【第四步】判断当前isa正在释放

    • 是,不需要Retain操作,直接返回

    • 不是,进入【第五步】

  • 【第五步】通过bitsRC_ONE进行Retain操作,引用计数+1,将状态赋值carry,进入【第六步】

  • 【第六步】判断extra_rc是否存储已满

    • 存储已满,修改一些标记,设置isaextra_rchas_sidetable_rc,进入【第七步】

      • RC_HALF表示砍半,将一半存储在extra_rc
    • 未存满,进入【第八步】

  • 【第七步】将另一半存储到散列表SideTable

  • 【第八步】返回,retain流程结束

release

进入rootRetain函数:

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;       
}

release流程:

  • 【第一步】判断如果是TaggedPointer

    • 是,什么都不处理,直接返回

    • 不是,进入【第二步】

  • 【第二步】判断是纯isa,并且是一个类

    • 是,不需要Release操作,直接返回

    • 不是,进入【第三步】

  • 【第三步】如果是纯isa,但不是类

    • 是,使用散列表,进行Release操作

    • 不是,进入【第四步】

  • 【第四步】判断当前isa正在释放

    • 是,不需要Release操作,直接返回

    • 不是,进入【第五步】

  • 【第五步】通过bitsRC_ONE进行Release操作,引用计数-1,将状态赋值carry,进入【第六步】

  • 【第六步】判断extra_rc是否减空

    • 是,进入【第七步】

    • 不是,进入【第八步】

  • 【第七步】进入underflow代码流程,判断当前已使用散列表存储

    • 是,从散列表中取出一半

      • 如果散列表中取空了,标记emptySideTable

      • 如果从散列表中取出内容,进行-1操作,赋值extra_rc

      • 通过emptySideTable标记,修改has_sidetable_rc

      • 存储到isabits

      • 如果存储失败,进行补救处理

    • 不是,进入【第八步】

  • 【第八步】进入deallocate代码流程

    • 调用对象的dealloc方法

    • 返回,release流程结束

SideTable的结构

散列表本质就是一张哈希表。

retain操作使用SideTable进行存储时,会进入sidetable_retain函数:

id 
objc_object::sidetable_retain(bool locked) {
#if SUPPORT_NONPOINTER_ISA 
    ASSERT(!isa.nonpointer); 
#endif 
    SideTable& table = SideTables()[this];
    
    if (!locked) table.lock(); 
    size_t& refcntStorage = table.refcnts[this]; 
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { 
        refcntStorage += SIDE_TABLE_RC_ONE; 
    } 
    table.unlock(); 
    
    return (id)this; 
}
  • 本质上操作的是SideTable结构,在SideTables中,找到当前对象对应的一张散列表。

这说明散列表不止只有一张,它的底层使用StripedMap

template<typename T> 
class StripedMap { 
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR 
    enum { StripeCount = 8 }; 
#else 
    enum { StripeCount = 64 }; 
#endif


    ... 
}
  • 根据不同系统架构,可创建864SideTable。在iOS设置上,只能创建8张。

查看SideTable的结构:

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts; 
    weak_table_t weak_table; 
    
    SideTable() { 
        memset(&weak_table, 0, sizeof(weak_table));
    } 
    
    ~SideTable() { 
        _objc_fatal("Do not delete SideTable."); 
    }
    
    void lock() { 
        slock.lock(); 
    }
    
    void unlock() {
        slock.unlock(); 
    }
    
    void forceReset() { 
       slock.forceReset();
    } 
    
    // Address-ordered lock discipline for a pair of side tables. 
    template<HaveOld, HaveNew> 
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew> 
    static void unlockTwo(SideTable *lock1, SideTable *lock2); 
};
  • 包含一把锁、一张引用计数表和一张弱引用表;

  • SideTable的存取,会牵扯加锁和解锁的耗时操作,为了线程安全。所以当多个对象同时操作SideTable时,为了保证效率,采用多张SideTable表分散压力;

  • 当对象分散使用多张表时,当表中的对象全部释放后,该表也可以释放,这样可以及时回收内存;

  • 由于开表所消耗的内存过大,如果针对每个对象都开一张表,会造成很大程度的内存浪费。

所以SideTable的核心作用,对引用计数表和弱引用表进行处理。

SideTable的存取

extra_rc存满后,只会分出一半存储到SideTable中。

因为对SideTable的操作,需要经过加锁、解锁保证线程安全,相对耗时。如果extra_rc存满后全部导入SideTable中,在引用计数-1的时候,需要频繁对SideTable进行操作,效率太低。

相比extra_rc的操作,它通过isa的位运算得到,可以直接进行+1-1的操作,效率要比SideTable高很多。

下面对散列表SideTable中引用计数的存取进行底层分析。

sidetable_retain

对散列表中的引用计数+1

id 
objc_object::sidetable_retain(bool locked) {
#if SUPPORT_NONPOINTER_ISA 
    ASSERT(!isa.nonpointer); 
#endif 
    SideTable& table = SideTables()[this]; 
    
    if (!locked) table.lock(); 
    size_t& refcntStorage = table.refcnts[this]; 
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { 
        refcntStorage += SIDE_TABLE_RC_ONE;
    } 
    
    table.unlock(); 
    return (id)this;
}
  • 通过当前对象,找到所属的SideTable

  • 通过当前对象,找到在引用计数表的所属空间;

  • 所属空间中并不是直接存储引用计数,而是使用位域存储很多信息;

  • 核心代码:refcntStorage += SIDE_TABLE_RC_ONE

找到SIDE_TABLE_RC_ONE的定义:

#define SIDE_TABLE_WEAKLY_REFERENCED     (1UL<<0) 
#define SIDE_TABLE_DEALLOCATING          (1UL<<1) // MSB-ward of weak bit 
#define SIDE_TABLE_RC_ONE                (1UL<<2) // MSB-ward of deallocating bit 
#define SIDE_TABLE_RC_PINNED             (1UL<<(WORD_BITS-1))
  • 1左移2位,结果为4,转为十六进制为100

  • 因为真正的refcnt在位域中的第2位上存储,+100的目的就是不影响前面两位,实则引用计数+1

  • SIDE_TABLE_WEAKLY_REFERENCED:弱引用标记;

  • SIDE_TABLE_DEALLOCATING:是否正在析构;

  • SIDE_TABLE_RC_ONE:引用计数;

  • SIDE_TABLE_RC_PINNED:标记引用计数是否越界。

sidetable_addExtraRC_nolock

extra_rc存储已满,将一半存储到散列表中:

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { 
    ASSERT(isa.nonpointer); 
    SideTable& table = SideTables()[this]; 
    
    size_t& refcntStorage = table.refcnts[this]; 
    size_t oldRefcnt = refcntStorage;
    
    // isa-side bits should not be set here 
    
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); 
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); 
    
    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true; 
    
    uintptr_t carry;
    size_t newRefcnt = addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); 
    
    if (carry) { 
        refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK); 
        return true;
    } else {
        refcntStorage = newRefcnt; 
        return false;
    } 
}
  • 同样对delta_rc进行左移SIDE_TABLE_RC_SHIFT位。

找到SIDE_TABLE_RC_SHIFT的定义:

#define SIDE_TABLE_RC_SHIFT 2
  • 进行左移2位

sidetable_subExtraRC_nolock

extra_rc减空,从散列表中读取一半:

objc_object::SidetableBorrow 
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc) { 
    ASSERT(isa.nonpointer); 
    SideTable& table = SideTables()[this]; 
    RefcountMap::iterator it = table.refcnts.find(this); 
    
    if (it == table.refcnts.end() || it->second == 0) { 
        // Side table retain count is zero. Can't borrow. 
        return { 0, 0 }; 
    } 
    
    size_t oldRefcnt = it->second; 
    
    // isa-side bits should not be set here 
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); 
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
    
    size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT); 
    ASSERT(oldRefcnt > newRefcnt);  // shouldn't underflow 
    
    it->second = newRefcnt; 
    return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT }; 
}
  • 对散列表中的refcnt砍半,同样先经过左移2位的操作;

  • 将剩余的refcnt返回给上层函数,需要先右移2位,恢复为正常的引用计数。

WeakTable

rootRetainCount

打印对象的引用计数,代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSObject *objc = [[NSObject alloc] init];
    
    NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
}

打印输出结果:

objc:1

在老版本的objc源码中,对象alloc默认为0。为了避免它的释放,在rootRetainCount方法中,系统对引用计数默认+1

iOS底层原理探索-----内存管理 (一)

  • 仅在读取时对引用计数+1

  • 实际上extra_rc中的引用计数仍然为0

在最新版本的objc4-818.2源码中,对rootRetainCount函数进行了修改:

inline uintptr_t 
objc_object::rootRetainCount() { 
    if (isTaggedPointer()) return (uintptr_t)this;
    
    sidetable_lock(); 
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED); 
    
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc; 
        if (bits.has_sidetable_rc) { 
            rc += sidetable_getExtraRC_nolock(); 
        } 
        
        sidetable_unlock();
        return rc; 
    } 
    
    sidetable_unlock(); 
    return sidetable_retainCount(); 
}
  • 没有+1的处理,直接获取extra_rc。如果使用散列表,再加上散列表中的引用计数。

这里的变化,源于alloc的底层修改。当对象alloc时,调用initIsa函数:

iOS底层原理探索-----内存管理 (一)

  • isa初始化时,引用计数已经设置为1

验证weakObjc的引用计数

将对象赋值给使用__weak修饰的变量,打印它的引用计数:

- (void)viewDidLoad { 
    [super viewDidLoad];
    
    NSObject *objc = [[NSObject alloc] init];
    __weak typeof(objc) weakObjc = objc; 
    
    NSLog(@"objc:%ld,%@",CFGetRetainCount((__bridge CFTypeRef)objc), objc); 
    NSLog(@"weakObjc:%ld,%@",CFGetRetainCount((__bridge CFTypeRef)weakObjc), weakObjc); 
}

打印结果:

objc:1,<NSObject: 0x28132c7f0> 
weakObjc:2,<NSObject: 0x28132c7f0>
  • 对象被弱引用变量持有,并不会对自身的引用计数+1,所以objc仍然输出为1

但问题是,为什么weakObjc的引用计数输出为2?。

weak的底层探索

weak修饰的变量处设置断点,运行项目,查看汇编代码:

iOS底层原理探索-----内存管理 (一)

调用objc_initWeak函数,来自libobjc框架:

iOS底层原理探索-----内存管理 (一)

objc4-818.2源码中,进入objc_initWeak函数:

id 
objc_initWeak(id *location, id newObj) { 
    if (!newObj) { 
    
       *location = nil;
       return nil; 
    } 
    
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); 
}
  • storeWeak是一个模板类,内部逻辑具有高度可封装,通过传入的参数不同,执行不同的逻辑分支。

storeWeak

进入storeWeak函数:

template <HaveOld haveOld, HaveNew haveNew, 
          enum CrashIfDeallocating crashIfDeallocating>
          
static id 
storeWeak(id *location, objc_object *newObj) {

    ASSERT(haveOld || haveNew); 
    if (!haveNew) ASSERT(newObj == nil); 
    
    //location:弱引用对象指针 
    //newObj:原始对象 
    
    Class previouslyInitializedClass = nil; 
    id oldObj; 
    SideTable *oldTable; 
    SideTable *newTable; 
    
    // Acquire locks for old and new values. 
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
retry:
    //判断haveOld 
    if (haveOld) { 
        oldObj = *location; 
        oldTable = &SideTables()[oldObj]; 
    } else { 
        //在objc_initWeak中调用时,传入的DontHaveOld,不存在 
        //将oldTable设置为nil 
        oldTable = nil; 
    }
    
    //判断haveNew 
    if (haveNew) { 
        //在objc_initWeak中调用时,传入的DoHaveNew,存在 
        //找到原始对象所在的SideTable 
        newTable = &SideTables()[newObj]; 
    } else {
        newTable = nil; 
    }
    
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }
    
    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    
    //防止弱引用机制之间的死锁 
    //确保Class的初始化 
    //为弱引用对象未初始化isa
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        
        if (cls != previouslyInitializedClass  &&!((objc_class *)cls)->isInitialized()) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);
            
            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            //赋值previouslyInitializedClass,防止重复初始化
            previouslyInitializedClass = cls;
            
            goto retry;
        }
    }
    
    // Clean up old value, if any.
    if (haveOld) {
        //存在旧值,将其移除
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    
    // Assign new value, if any. 
    if (haveNew) { 
        //调用weak_register_no_lock函数,传入当前对象所在散列表中的weak_table,并传入原始对象和弱引用对象指针 
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating); 
                                  
           // weak_register_no_lock returns nil if weak store should be rejected 
           // Set is-weakly-referenced bit in refcount table. 
           if (!newObj->isTaggedPointerOrNil()) {
               newObj->setWeaklyReferenced_nolock(); 
           } 
           
           // Do not set *location anywhere else. That would introduce a race. 
           //将原始对象赋值给弱引用对象 
           *location = (id)newObj; 
     } else { 
         // No new value. The storage is not changed. 
     }
     
     SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
     
     // This must be called without the locks held, as it can invoke
     // arbitrary code. In particular, even if _setWeaklyReferenced 
     // is not implemented, resolveInstanceMethod: may be, and may 
     // call back into the weak reference machinery. 
     
     callSetWeaklyReferenced((id)newObj); 
     
     return (id)newObj;
}
  • 将弱引用对象指针location和原始对象newObj函数;

  • 判断haveOld,在objc_initWeak中调用时,传入的DontHaveOld,不存在

    • oldTable设置为nil
  • 判断haveNew,在objc_initWeak中调用时,传入的DoHaveNew,存在:

    • 找到原始对象所在的SideTable
  • 判断haveNew和原始对象,防止弱引用机制之间的死锁,确保Class的初始化,为弱引用对象未初始化isa

  • 判断haveOld,存在旧值,将其移除;

  • 判断haveNew存在:

    • 调用weak_register_no_lock函数,传入当前对象所在散列表中的weak_table,并传入原始对象和弱引用对象指针;

    • 将原始对象赋值给弱引用对象。

  • 将原始对象返回,流程结束。

weak_register_no_lock

进入weak_register_no_lock函数:

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) { 
    //referent_id:原始对象 
    //referrer_id:弱引用对象的指针 
    
    objc_object *referent = (objc_object *)referent_id; 
    objc_object **referrer = (objc_object **)referrer_id; 
    
    if (referent->isTaggedPointerOrNil()) return referent_id; 
    
    // ensure that the referenced object is viable
    //确保引用的对象是可用的 
    if (deallocatingOptions == ReturnNilIfDeallocating || deallocatingOptions == CrashIfDeallocating) { 
        bool deallocating; 
        if (!referent->ISA()->hasCustomRR()) { 
            deallocating = referent->rootIsDeallocating(); 
        } else { 
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held. 
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL)) lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference), referent->getIsa()); 
            if ((IMP)allowsWeakReference == _objc_msgForward) { 
                return nil; 
            } 
            
            deallocating = ! (*allowsWeakReference)(referent, @selector(allowsWeakReference)); 
        } 
        
        if (deallocating) { 
            if (deallocatingOptions == CrashIfDeallocating) { 
                _objc_fatal("Cannot form weak reference to instance (%p) of " 
                            "class %s. It is possible that this object was "
                            "over-released, or is in the process of deallocation.",
                            (void*)referent, object_getClassName((id)referent)); 
            } else { 
                return nil; 
            } 
        } 
    }
    
    // now remember it and where it is being stored 
    weak_entry_t *entry; 
    //判断在表中是否能找到对象的存储
    if ((entry = weak_entry_for_referent(weak_table, referent))) { 
        //已存在,将弱引用对象追加到表中
        append_referrer(entry, referrer); 
    } else {
        //不存在,创建weak_entry_t,将原始对象和弱引用对象关联 
        weak_entry_t new_entry(referent, referrer); 
        //重新开辟一张表 
        weak_grow_maybe(weak_table); 
        //将new_entry插入到表中
        weak_entry_insert(weak_table, &new_entry); 
    } 
    
    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change. 
    
    return referent_id; 
}
  • 传入原始对象referent_id和弱引用对象的指针referrer_id

  • 一系列判断,确保引用的对象是可用的;

  • 判断在表中是否能找到对象的存储:

    • 已存在,将弱引用对象追加到表中;

    • 不存在:

      • 创建weak_entry_t,将原始对象和弱引用对象关联;

      • 重新开辟一张表;

      • new_entry插入到表中。

  • 将原始对象返回,流程结束。

append_referrer

进入append_referrer函数:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) {
    if (! entry->out_of_line()) { 
    
        // Try to insert inline. 
        //传入原始对象所属的weak_entry_t结构体指针和弱引用对象指针 
        //找到weak_entry_t下的inline_referrers,一个对象可能被赋值给多个弱引用对象 
        //循环找到空位,如果存在空位直接赋值 

        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { 
            if (entry->inline_referrers[i] == nil) { 
                entry->inline_referrers[i] = new_referrer; return;
            } 
        } 

        // Couldn't insert inline. Allocate out of line. 
        //没有空位,重新创建new_referrers 
        weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); 

        // This constructed table is invalid, but grow_refs_and_insert 
        // will fix it and rehash it. 
        //将现有entry下的inline_referrers中的元素,循环导入到新创建的new_referrers中 
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i]; 
        } 

        //相关成员变量的覆盖 
        entry->referrers = new_referrers; 
        entry->num_refs = WEAK_INLINE_COUNT; 
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; 
        entry->mask = WEAK_INLINE_COUNT-1; 
        entry->max_hash_displacement = 0; 
    } 

    ASSERT(entry->out_of_line()); 
    
    //扩容相关操作 
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { 
        return grow_refs_and_insert(entry, new_referrer); 
    }
    
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); 
    size_t index = begin; 
    size_t hash_displacement = 0; 
    while (entry->referrers[index] != nil) { 
        hash_displacement++; 
        index = (index+1) & entry->mask; 
        if (index == begin) bad_weak_table(entry); 
    }
    
    if (hash_displacement > entry->max_hash_displacement) { 
        entry->max_hash_displacement = hash_displacement; 
    } 
    
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++; 
}
  • 传入原始对象所属的weak_entry_t结构体指针和弱引用对象指针;

  • 找到weak_entry_t下的inline_referrers,一个对象可能被赋值给多个弱引用对象;

  • 循环找到空位,如果存在空位直接赋值;

  • 没有空位,重新创建new_referrers

  • 将现有entry下的inline_referrers中的元素,循环导入到新创建的new_referrers中;

  • 相关成员变量的覆盖;

  • 扩容相关操作。

weakObjc引用计数为2的原因

查看汇编代码:

iOS底层原理探索-----内存管理 (一)

  • 弱引用对象在使用时,会调用objc_loadWeakRetained函数;

  • 汇编代码中的两次调用,第一次在执行CFGetRetainCount函数之前,第二次在执行NSLog方法之前。

objc4-818.2源码中,进入objc_loadWeakRetained函数:

id 
objc_loadWeakRetained(id *location) { 
    id obj; 
    id result;
    Class cls;
    
    SideTable *table; 
    
retry: 
    
    // fixme std::atomic this load 
    //对弱引用对象指针取值 
    obj = *location; 
    
    if (obj->isTaggedPointerOrNil()) return obj; 
    table = &SideTables()[obj]; 
    table->lock(); 
    if (*location != obj) {
        table->unlock(); 
        goto retry; 
    }
    
    //赋值给result临时变量 
    result = obj; 
    cls = obj->ISA(); 
    if (! cls->hasCustomRR()) { 
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then. 
        
        ASSERT(cls->isInitialized());
        
        //调用retain函数,对引用计数+1 
        
        if (! obj->rootTryRetain()) { 
            result = nil;
        } 
    } else { 
        // Slow case. We must check for +initialize and call it outside 
        // the lock if necessary in order to avoid deadlocks. 
        // Use lookUpImpOrForward so we can avoid the assert in 
        // class_getInstanceMethod, since we intentionally make this 
        // callout with the lock held. 
        
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { 
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls); 
            if ((IMP)tryRetain == _objc_msgForward) { 
                result = nil; 
            } else if (! (*tryRetain)(obj, @selector(retainWeakReference))) { 
                result = nil;
            } 
        } else { 
            table->unlock(); 
            class_initialize(cls, obj); 
            goto retry; 
        }
    } 
    
    table->unlock(); 
    return result;
}
  • 传入弱引用对象指针;

  • 对弱引用对象指针取值,赋值给obj,相当于原始对象;

  • obj赋值给result,此时引用计数不变;

  • 核心代码:if (! obj->rootTryRetain()),调用obj调用retain函数,引用计数+1

使用弱引用对象,触发objc_loadWeakRetained函数,为对象进行一次retain操作。

这样做的目的,避免弱引用对象在使用过程中,由于原始对象被释放,导致所有正在使用的弱引用对象全部取值异常,造成大面积的连锁反应。

objc_loadWeakRetained函数执行完毕,临时变量会释放,自动恢复对象的引用计数。

采用这种方式的好处,让原始对象和弱引用对象更加独立,对强弱引用对象进行分开管理。

weak流程图

iOS底层原理探索-----内存管理 (一)

转载自:https://juejin.cn/post/7007627353832554532
评论
请登录