1. 内存布局

当程序运行时,系统会开辟内核区、程序使用的内存五大区和保留区

1.1 内核区

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

1.2 内存五大区

内存主要分为栈区、堆区、全局区、常量区、代码区五大区域
image.png

1.2.1 栈区(Stack

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

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

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

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

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

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

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

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

1.2.2 堆区(Heap

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

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

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

  • 堆区的存储

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

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

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

1.2.3 全局静态区(.bss & .data

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

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

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

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

1.2.4 常量区(.rodata

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

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

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

1.2.5 代码区(.text

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

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

1.3 保留区

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

1.4 面试题

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

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

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

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

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

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

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

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

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

2. 内存管理方案

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

2.1 MRC

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

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

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

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

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

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

2.2 ARC

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

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

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

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

3. Tagged Pointer

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

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

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

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

3.1 概述

NSString为例,读取一个常规的NSString,通过栈区存储的指针地址,找到堆区空间,然后从堆区读取到字符串的值,整个读取流程效率较低

所以,系统对其进行优化,如果NSString存储的字符串长度较短,会使用Tagged Pointer存储

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

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

案例:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSString *str = [NSString stringWithFormat:@"kc"];
  4. NSLog(@"%p-%@-%@",str,str,str.class);
  5. }
  6. -------------------------
  7. //输出以下内容:
  8. 0x800000000031b592-kc-NSTaggedPointerString
  • 上述案例字符串的所属为NSTaggedPointerString

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

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

NSString内存管理的三种类型:

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

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

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

3.2 结构

3.2.1 模拟器

objc4-818.2源码中,找到Tagged Pointer的解码方法

进入_objc_decodeTaggedPointer函数

  1. #if __arm64__
  2. // ARM64 uses a new tagged pointer scheme where normal tags are in
  3. // the low bits, extended tags are in the high bits, and half of the
  4. // extended tag space is reserved for unobfuscated payloads.
  5. # define OBJC_SPLIT_TAGGED_POINTERS 1
  6. #else
  7. # define OBJC_SPLIT_TAGGED_POINTERS 0
  8. #endif
  9. static inline uintptr_t
  10. _objc_decodeTaggedPointer(const void * _Nullable ptr)
  11. {
  12. uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
  13. #if OBJC_SPLIT_TAGGED_POINTERS
  14. uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
  15. value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
  16. value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
  17. #endif
  18. return value;
  19. }
  • 调用_objc_decodeTaggedPointer_noPermute函数,返回指针
  • 判断OBJC_SPLIT_TAGGED_POINTERS,符合条件进行位运算

进入_objc_decodeTaggedPointer_noPermute函数

  1. static inline uintptr_t
  2. _objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
  3. {
  4. uintptr_t value = (uintptr_t)ptr;
  5. #if OBJC_SPLIT_TAGGED_POINTERS
  6. if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
  7. return value;
  8. #endif
  9. return value ^ objc_debug_taggedpointer_obfuscator;
  10. }
  • objc_debug_taggedpointer_obfuscator进行按位异或
  • objc_debug_taggedpointer_obfuscator,可以理解为随机数

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

进入initializeTaggedPointerObfuscator函数

  1. static void
  2. initializeTaggedPointerObfuscator(void)
  3. {
  4. // && dyld_program_sdk_at_least(dyld_fall_2018_os_versions)
  5. if (!DisableTaggedPointerObfuscation) {
  6. // Pull random data into the variable, then shift away all non-payload bits.
  7. arc4random_buf(&objc_debug_taggedpointer_obfuscator,
  8. sizeof(objc_debug_taggedpointer_obfuscator));
  9. objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
  10. #if OBJC_SPLIT_TAGGED_POINTERS
  11. // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
  12. objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
  13. // Shuffle the first seven entries of the tag permutator.
  14. int max = 7;
  15. for (int i = max - 1; i >= 0; i--) {
  16. int target = arc4random_uniform(i + 1);
  17. swap(objc_debug_tag60_permutations[i],
  18. objc_debug_tag60_permutations[target]);
  19. }
  20. #endif
  21. } else {
  22. // Set the obfuscator to zero for apps linked against older SDKs,
  23. // in case they're relying on the tagged pointer representation.
  24. objc_debug_taggedpointer_obfuscator = 0;
  25. }
  26. }
  • 判断是否开启混淆,开启将objc_debug_taggedpointer_obfuscator赋值随机数,否则赋值为0

系统生成的Tagged Pointer是编码后的,我们要想了解它的结构,需要对其进行解码

objc_debug_taggedpointer_obfuscator为全局静态变量,我们可以在程序中,使用extern修饰,将其导出,自己实现一个解码函数,使用相同的值,将指针再次按位异或即可还原

  1. extern uintptr_t objc_debug_taggedpointer_obfuscator;
  2. uintptr_t
  3. kc_objc_decodeTaggedPointer(id ptr)
  4. {
  5. return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
  6. }

案例:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSString *str = [NSString stringWithFormat:@"kc"];
  4. NSLog(@"%p-%@-%@ - 0x%lx",str,str,str.class,kc_objc_decodeTaggedPointer(str));
  5. }
  6. -------------------------
  7. //输出以下内容:
  8. 0xa0000000000636b2-kc-NSTaggedPointerString - 0xa0000000000636b2
  • 使用模拟器运行程序,解码后的指针地址:0xa0000000000636b2

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

  1. (lldb) p/t 0xa0000000000636b2
  2. (unsigned long) $0 = 0b1010000000000000000000000000000000000000000001100011011010110010
  • 0b表示二进制
  • 高地址第一位1,表示该isaTagged Pointer类型

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

  1. (lldb) p/t $0 >> 4
  2. (unsigned long) $1 = 0b0000101000000000000000000000000000000000000000000110001101101011

低地址最后16位,每8位进行一次打印

  1. (lldb) po 0b01101011
  2. 107
  3. (lldb) po 0b01100011
  4. 99

里面存储的内容,其实就是字符的assic

  1. (lldb) po (char)107
  2. 'k'
  3. (lldb) po (char)99
  4. 'c'

isa转为二进制,高地址的前4位,第一位表示该isaTagged Pointer类型,后面三位010表示Tagged Pointer所存储的类型

  1. (lldb) po 0b010
  2. 2

对应objc源码中的类型

  1. // 60-bit payloads
  2. OBJC_TAG_NSAtom = 0,
  3. OBJC_TAG_1 = 1,
  4. OBJC_TAG_NSString = 2,
  5. OBJC_TAG_NSNumber = 3,
  6. OBJC_TAG_NSIndexPath = 4,
  7. OBJC_TAG_NSManagedObjectID = 5,
  8. OBJC_TAG_NSDate = 6,
  9. ...

3.2.2 真机

Tagged Pointer结构,在真机上更为复杂
image.png

实现真机环境解码函数

  1. #define kc_OBJC_TAG_INDEX_MASK 0x7UL
  2. #define kc_OBJC_TAG_INDEX_SHIFT 0
  3. extern uint8_t objc_debug_tag60_permutations[8];
  4. uintptr_t kc_objc_obfuscatedTagToBasicTag(uintptr_t tag) {
  5. for (unsigned i = 0; i < 7; i++)
  6. if (objc_debug_tag60_permutations[i] == tag)
  7. return i;
  8. return 7;
  9. }
  10. uintptr_t
  11. kc_objc_decodeTaggedPointer(id ptr)
  12. {
  13. uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
  14. uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;
  15. value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
  16. value |= kc_objc_obfuscatedTagToBasicTag(basicTag) << kc_OBJC_TAG_INDEX_SHIFT;
  17. return value;
  18. }
  19. static inline uintptr_t kc_objc_basicTagToObfuscatedTag(uintptr_t tag) {
  20. return objc_debug_tag60_permutations[tag];
  21. }
  22. void *
  23. kc_objc_encodeTaggedPointer(uintptr_t ptr)
  24. {
  25. uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
  26. uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;
  27. uintptr_t permutedTag = kc_objc_basicTagToObfuscatedTag(basicTag);
  28. value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
  29. value |= permutedTag << kc_OBJC_TAG_INDEX_SHIFT;
  30. return (void *)value;
  31. }

上述案例,使用真机运行

  1. 0x800000000031b592-kc-NSTaggedPointerString - 0x800000000031b592

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

  1. (lldb) p/t 0x800000000031b592
  2. (unsigned long) $0 = 0b1000000000000000000000000000000000000000001100011011010110010010
  • 和模拟器的区别,高地址第一位,没有变化,表示该isaTagged Pointer类型
  • 但类型的位置发生变化,存储在低地址最后三位

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

  1. (lldb) po 0b0010
  2. 2

查看NSNumber类型

  1. number0x8000000000000313-6 -__NSCFNumber- 0x8000000000000313
  2. number10x800000000000032b-6 -__NSCFNumber- 0x800000000000032b

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

  1. (lldb) p/t 0x8000000000000313
  2. (unsigned long) $0 = 0b1000000000000000000000000000000000000000000000000000001100010011
  3. (lldb) p/t 0x800000000000032b
  4. (unsigned long) $1 = 0b1000000000000000000000000000000000000000000000000000001100101011
  • 低地址最后三位011表示NSNumber的类型
  • 低地址4~7位,分别存储00100101,表示存储的基本数据类型。例如:intlongdoublefloat

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

  • char0

  • short1

  • int2

  • long3

  • float4

  • double5

3.3 关闭混淆

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

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

搜索DisableTaggedPointerObfuscation

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

在测试项目中,添加OBJC_DISABLE_TAG_OBFUSCATION环境变量,将其设置为YES
image.png

运行项目:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSNumber *number = @6;
  4. NSLog(@"number:%p-%@ -%@- 0x%lx",number,number,number.class,(uintptr_t)number);
  5. }
  6. -------------------------
  7. //输出以下内容:
  8. number0x8000000000000313-6 -__NSCFNumber- 0x8000000000000313
  • 此时,直接获得未编码的isa

3.4 内存管理

案例:

  1. - (void)taggedPointerDemo {
  2. self.queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  3. for (int i = 0; i<10000; i++) {
  4. dispatch_async(self.queue, ^{
  5. self.nameStr = [NSString stringWithFormat:@"kc"];
  6. NSLog(@"%@",self.nameStr);
  7. });
  8. }
  9. }
  10. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  11. for (int i = 0; i<100000; i++) {
  12. dispatch_async(self.queue, ^{
  13. self.nameStr = [NSString stringWithFormat:@"kc_和谐学习不急不躁"];
  14. NSLog(@"%@",self.nameStr);
  15. });
  16. }
  17. }
  • taggedPointerDemo的运行,不会出现任何问题。因为字符串kc会被优化成NSTaggedPointerString类型

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

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

objc源码中,找到rootRetain函数
image.png

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

进入rootRelease函数
image.png

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

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

3.5 总结

  • 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位,用于存储值

4. SideTables

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

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

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

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

4.1 retain

objc源码中,进入rootRetain函数

  1. ALWAYS_INLINE id
  2. objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
  3. {
  4. //1、判断如果是TaggedPointer,什么都不处理,直接返回
  5. if (slowpath(isTaggedPointer())) return (id)this;
  6. bool sideTableLocked = false;
  7. bool transcribeToSideTable = false;
  8. isa_t oldisa;
  9. isa_t newisa;
  10. oldisa = LoadExclusive(&isa.bits);
  11. if (variant == RRVariant::FastOrMsgSend) {
  12. // These checks are only meaningful for objc_retain()
  13. // They are here so that we avoid a re-load of the isa.
  14. if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
  15. ClearExclusive(&isa.bits);
  16. if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
  17. return swiftRetain.load(memory_order_relaxed)((id)this);
  18. }
  19. return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
  20. }
  21. }
  22. if (slowpath(!oldisa.nonpointer)) {
  23. // a Class is a Class forever, so we can perform this check once
  24. // outside of the CAS loop
  25. //2、如果是纯isa,判断如果是一个类,也不需要Retain操作
  26. if (oldisa.getDecodedClass(false)->isMetaClass()) {
  27. ClearExclusive(&isa.bits);
  28. return (id)this;
  29. }
  30. }
  31. do {
  32. transcribeToSideTable = false;
  33. newisa = oldisa;
  34. if (slowpath(!newisa.nonpointer)) {
  35. //3、如果是纯isa,使用散列表,进行Retain操作
  36. ClearExclusive(&isa.bits);
  37. if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
  38. else return sidetable_retain(sideTableLocked);
  39. }
  40. // don't check newisa.fast_rr; we already called any RR overrides
  41. if (slowpath(newisa.isDeallocating())) {
  42. //4、如果当前isa正在释放,不需要Retain操作
  43. ClearExclusive(&isa.bits);
  44. if (sideTableLocked) {
  45. ASSERT(variant == RRVariant::Full);
  46. sidetable_unlock();
  47. }
  48. if (slowpath(tryRetain)) {
  49. return nil;
  50. } else {
  51. return (id)this;
  52. }
  53. }
  54. //5、通过bits对RC_ONE进行Retain操作,引用计数+1,将状态赋值carry
  55. uintptr_t carry;
  56. newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
  57. if (slowpath(carry)) {
  58. // newisa.extra_rc++ overflowed
  59. if (variant != RRVariant::Full) {
  60. ClearExclusive(&isa.bits);
  61. return rootRetain_overflow(tryRetain);
  62. }
  63. // Leave half of the retain counts inline and
  64. // prepare to copy the other half to the side table.
  65. if (!tryRetain && !sideTableLocked) sidetable_lock();
  66. //6、存储已满,修改一些标记,设置isa的extra_rc和has_sidetable_rc
  67. //RC_HALF表示砍半,将一半存储在extra_rc
  68. sideTableLocked = true;
  69. transcribeToSideTable = true;
  70. newisa.extra_rc = RC_HALF;
  71. newisa.has_sidetable_rc = true;
  72. }
  73. } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
  74. if (variant == RRVariant::Full) {
  75. if (slowpath(transcribeToSideTable)) {
  76. // Copy the other half of the retain counts to the side table.
  77. //7、将另一半存储到散列表SideTable中
  78. sidetable_addExtraRC_nolock(RC_HALF);
  79. }
  80. if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
  81. } else {
  82. ASSERT(!transcribeToSideTable);
  83. ASSERT(!sideTableLocked);
  84. }
  85. return (id)this;
  86. }

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流程结束

4.2 release

进入rootRetain函数

  1. ALWAYS_INLINE bool
  2. objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
  3. {
  4. //1、判断如果是TaggedPointer,什么都不处理,直接返回
  5. if (slowpath(isTaggedPointer())) return false;
  6. bool sideTableLocked = false;
  7. isa_t newisa, oldisa;
  8. oldisa = LoadExclusive(&isa.bits);
  9. if (variant == RRVariant::FastOrMsgSend) {
  10. // These checks are only meaningful for objc_release()
  11. // They are here so that we avoid a re-load of the isa.
  12. if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
  13. ClearExclusive(&isa.bits);
  14. if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
  15. swiftRelease.load(memory_order_relaxed)((id)this);
  16. return true;
  17. }
  18. ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
  19. return true;
  20. }
  21. }
  22. if (slowpath(!oldisa.nonpointer)) {
  23. // a Class is a Class forever, so we can perform this check once
  24. // outside of the CAS loop
  25. //2、如果是纯isa,判断如果是一个类,也不需要Release操作
  26. if (oldisa.getDecodedClass(false)->isMetaClass()) {
  27. ClearExclusive(&isa.bits);
  28. return false;
  29. }
  30. }
  31. retry:
  32. do {
  33. newisa = oldisa;
  34. if (slowpath(!newisa.nonpointer)) {
  35. //3、如果是纯isa,使用散列表,进行Release操作
  36. ClearExclusive(&isa.bits);
  37. return sidetable_release(sideTableLocked, performDealloc);
  38. }
  39. if (slowpath(newisa.isDeallocating())) {
  40. //4、如果当前isa正在释放,不需要Release操作
  41. ClearExclusive(&isa.bits);
  42. if (sideTableLocked) {
  43. ASSERT(variant == RRVariant::Full);
  44. sidetable_unlock();
  45. }
  46. return false;
  47. }
  48. // don't check newisa.fast_rr; we already called any RR overrides
  49. //5、通过bits对RC_ONE进行Release操作,引用计数-1,将状态赋值carry
  50. uintptr_t carry;
  51. newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
  52. if (slowpath(carry)) {
  53. // don't ClearExclusive()
  54. //6、如果extra_rc减空,进入underflow代码流程
  55. goto underflow;
  56. }
  57. } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
  58. if (slowpath(newisa.isDeallocating()))
  59. goto deallocate;
  60. if (variant == RRVariant::Full) {
  61. if (slowpath(sideTableLocked)) sidetable_unlock();
  62. } else {
  63. ASSERT(!sideTableLocked);
  64. }
  65. return false;
  66. underflow:
  67. // newisa.extra_rc-- underflowed: borrow from side table or deallocate
  68. // abandon newisa to undo the decrement
  69. newisa = oldisa;
  70. if (slowpath(newisa.has_sidetable_rc)) {
  71. //7、判断当前已使用散列表存储
  72. if (variant != RRVariant::Full) {
  73. ClearExclusive(&isa.bits);
  74. return rootRelease_underflow(performDealloc);
  75. }
  76. // Transfer retain count from side table to inline storage.
  77. if (!sideTableLocked) {
  78. ClearExclusive(&isa.bits);
  79. sidetable_lock();
  80. sideTableLocked = true;
  81. // Need to start over to avoid a race against
  82. // the nonpointer -> raw pointer transition.
  83. oldisa = LoadExclusive(&isa.bits);
  84. goto retry;
  85. }
  86. // Try to remove some retain counts from the side table.
  87. //从散列表中取出一半
  88. auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
  89. //如果散列表中取空了,标记emptySideTable
  90. bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
  91. //判断从散列表中取出内容
  92. if (borrow.borrowed > 0) {
  93. // Side table retain count decreased.
  94. // Try to add them to the inline count.
  95. bool didTransitionToDeallocating = false;
  96. //进行-1操作,赋值extra_rc
  97. //通过emptySideTable标记,修改has_sidetable_rc
  98. newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
  99. newisa.has_sidetable_rc = !emptySideTable;
  100. //存储到isa的bits中
  101. bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
  102. if (!stored && oldisa.nonpointer) {
  103. // Inline update failed.
  104. // Try it again right now. This prevents livelock on LL/SC
  105. // architectures where the side table access itself may have
  106. // dropped the reservation.
  107. //存储失败的补救处理
  108. uintptr_t overflow;
  109. newisa.bits =
  110. addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
  111. newisa.has_sidetable_rc = !emptySideTable;
  112. if (!overflow) {
  113. stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
  114. if (stored) {
  115. didTransitionToDeallocating = newisa.isDeallocating();
  116. }
  117. }
  118. }
  119. if (!stored) {
  120. // Inline update failed.
  121. // Put the retains back in the side table.
  122. ClearExclusive(&isa.bits);
  123. sidetable_addExtraRC_nolock(borrow.borrowed);
  124. oldisa = LoadExclusive(&isa.bits);
  125. goto retry;
  126. }
  127. // Decrement successful after borrowing from side table.
  128. if (emptySideTable)
  129. sidetable_clearExtraRC_nolock();
  130. if (!didTransitionToDeallocating) {
  131. if (slowpath(sideTableLocked)) sidetable_unlock();
  132. return false;
  133. }
  134. }
  135. else {
  136. // Side table is empty after all. Fall-through to the dealloc path.
  137. }
  138. }
  139. deallocate:
  140. // Really deallocate.
  141. //8、进入deallocate代码流程
  142. ASSERT(newisa.isDeallocating());
  143. ASSERT(isa.isDeallocating());
  144. if (slowpath(sideTableLocked)) sidetable_unlock();
  145. __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
  146. if (performDealloc) {
  147. //调用对象的dealloc方法
  148. ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
  149. }
  150. return true;
  151. }

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流程结束

4.3 SideTable的结构

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

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

  1. id
  2. objc_object::sidetable_retain(bool locked)
  3. {
  4. #if SUPPORT_NONPOINTER_ISA
  5. ASSERT(!isa.nonpointer);
  6. #endif
  7. SideTable& table = SideTables()[this];
  8. if (!locked) table.lock();
  9. size_t& refcntStorage = table.refcnts[this];
  10. if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
  11. refcntStorage += SIDE_TABLE_RC_ONE;
  12. }
  13. table.unlock();
  14. return (id)this;
  15. }
  • 本质上操作的是SideTable结构,在SideTables中,找到当前对象对应的一张散列表

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

  1. template<typename T>
  2. class StripedMap {
  3. #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
  4. enum { StripeCount = 8 };
  5. #else
  6. enum { StripeCount = 64 };
  7. #endif
  8. ...
  9. }
  • 根据不同系统架构,可创建864SideTable。在iOS设置上,只能创建8

查看SideTable的结构

  1. struct SideTable {
  2. spinlock_t slock;
  3. RefcountMap refcnts;
  4. weak_table_t weak_table;
  5. SideTable() {
  6. memset(&weak_table, 0, sizeof(weak_table));
  7. }
  8. ~SideTable() {
  9. _objc_fatal("Do not delete SideTable.");
  10. }
  11. void lock() { slock.lock(); }
  12. void unlock() { slock.unlock(); }
  13. void forceReset() { slock.forceReset(); }
  14. // Address-ordered lock discipline for a pair of side tables.
  15. template<HaveOld, HaveNew>
  16. static void lockTwo(SideTable *lock1, SideTable *lock2);
  17. template<HaveOld, HaveNew>
  18. static void unlockTwo(SideTable *lock1, SideTable *lock2);
  19. };
  • 包含一把锁、一张引用计数表和一张弱引用表
  • SideTable的存取,会牵扯加锁和解锁的耗时操作,为了线程安全。所以当多个对象同时操作SideTable时,为了保证效率,采用多张SideTable表分散压力
  • 当对象分散使用多张表时,当表中的对象全部释放后,该表也可以释放,这样可以及时回收内存
  • 由于开表所消耗的内存过大,如果针对每个对象都开一张表,会造成很大程度的内存浪费

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

4.4 SideTable的存取

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

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

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

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

4.4.1 sidetable_retain

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

  1. id
  2. objc_object::sidetable_retain(bool locked)
  3. {
  4. #if SUPPORT_NONPOINTER_ISA
  5. ASSERT(!isa.nonpointer);
  6. #endif
  7. SideTable& table = SideTables()[this];
  8. if (!locked) table.lock();
  9. size_t& refcntStorage = table.refcnts[this];
  10. if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
  11. refcntStorage += SIDE_TABLE_RC_ONE;
  12. }
  13. table.unlock();
  14. return (id)this;
  15. }
  • 通过当前对象,找到所属的SideTable
  • 通过当前对象,找到在引用计数表的所属空间
  • 所属空间中并不是直接存储引用计数,而是使用位域存储很多信息
  • 核心代码:refcntStorage += SIDE_TABLE_RC_ONE

找到SIDE_TABLE_RC_ONE的定义

  1. #define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
  2. #define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
  3. #define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
  4. #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:标记引用计数是否越界

4.4.2 sidetable_addExtraRC_nolock

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

  1. bool
  2. objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
  3. {
  4. ASSERT(isa.nonpointer);
  5. SideTable& table = SideTables()[this];
  6. size_t& refcntStorage = table.refcnts[this];
  7. size_t oldRefcnt = refcntStorage;
  8. // isa-side bits should not be set here
  9. ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
  10. ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
  11. if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
  12. uintptr_t carry;
  13. size_t newRefcnt =
  14. addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
  15. if (carry) {
  16. refcntStorage =
  17. SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
  18. return true;
  19. }
  20. else {
  21. refcntStorage = newRefcnt;
  22. return false;
  23. }
  24. }
  • 同样对delta_rc进行左移SIDE_TABLE_RC_SHIFT

找到SIDE_TABLE_RC_SHIFT的定义

  1. #define SIDE_TABLE_RC_SHIFT 2
  • 进行左移2位

4.4.3 sidetable_subExtraRC_nolock

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

  1. objc_object::SidetableBorrow
  2. objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
  3. {
  4. ASSERT(isa.nonpointer);
  5. SideTable& table = SideTables()[this];
  6. RefcountMap::iterator it = table.refcnts.find(this);
  7. if (it == table.refcnts.end() || it->second == 0) {
  8. // Side table retain count is zero. Can't borrow.
  9. return { 0, 0 };
  10. }
  11. size_t oldRefcnt = it->second;
  12. // isa-side bits should not be set here
  13. ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
  14. ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
  15. size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
  16. ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow
  17. it->second = newRefcnt;
  18. return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
  19. }
  • 对散列表中的refcnt砍半,同样先经过左移2位的操作
  • 将剩余的refcnt返回给上层函数,需要先右移2位,恢复为正常的引用计数

5. WeakTable

5.1 rootRetainCount

打印对象的引用计数

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSObject *objc = [[NSObject alloc] init];
  4. NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
  5. }
  6. -------------------------
  7. //输出以下内容:
  8. objc1

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

  • 仅在读取时对引用计数+1
  • 实际上extra_rc中的引用计数仍然为0

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

  1. inline uintptr_t
  2. objc_object::rootRetainCount()
  3. {
  4. if (isTaggedPointer()) return (uintptr_t)this;
  5. sidetable_lock();
  6. isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
  7. if (bits.nonpointer) {
  8. uintptr_t rc = bits.extra_rc;
  9. if (bits.has_sidetable_rc) {
  10. rc += sidetable_getExtraRC_nolock();
  11. }
  12. sidetable_unlock();
  13. return rc;
  14. }
  15. sidetable_unlock();
  16. return sidetable_retainCount();
  17. }
  • 没有+1的处理,直接获取extra_rc。如果使用散列表,再加上散列表中的引用计数

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

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

5.2 验证weakObjc的引用计数

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

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSObject *objc = [[NSObject alloc] init];
  4. __weak typeof(objc) weakObjc = objc;
  5. NSLog(@"objc:%ld,%@",CFGetRetainCount((__bridge CFTypeRef)objc), objc);
  6. NSLog(@"weakObjc:%ld,%@",CFGetRetainCount((__bridge CFTypeRef)weakObjc), weakObjc);
  7. }
  8. -------------------------
  9. //输出以下内容:
  10. objc1,<NSObject: 0x28132c7f0>
  11. weakObjc2,<NSObject: 0x28132c7f0>
  • 对象被弱引用变量持有,并不会对自身的引用计数+1,所以objc仍然输出为1
  • 但问题是,为什么weakObjc的引用计数输出为2

5.3 weak的底层探索

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

调用objc_initWeak函数,来自libobjc框架
image.png

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

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

5.2.1 storeWeak

进入storeWeak函数

  1. template <HaveOld haveOld, HaveNew haveNew,
  2. enum CrashIfDeallocating crashIfDeallocating>
  3. static id
  4. storeWeak(id *location, objc_object *newObj)
  5. {
  6. ASSERT(haveOld || haveNew);
  7. if (!haveNew) ASSERT(newObj == nil);
  8. //location:弱引用对象指针
  9. //newObj:原始对象
  10. Class previouslyInitializedClass = nil;
  11. id oldObj;
  12. SideTable *oldTable;
  13. SideTable *newTable;
  14. // Acquire locks for old and new values.
  15. // Order by lock address to prevent lock ordering problems.
  16. // Retry if the old value changes underneath us.
  17. retry:
  18. //判断haveOld
  19. if (haveOld) {
  20. oldObj = *location;
  21. oldTable = &SideTables()[oldObj];
  22. } else {
  23. //在objc_initWeak中调用时,传入的DontHaveOld,不存在
  24. //将oldTable设置为nil
  25. oldTable = nil;
  26. }
  27. //判断haveNew
  28. if (haveNew) {
  29. //在objc_initWeak中调用时,传入的DoHaveNew,存在
  30. //找到原始对象所在的SideTable
  31. newTable = &SideTables()[newObj];
  32. } else {
  33. newTable = nil;
  34. }
  35. SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
  36. if (haveOld && *location != oldObj) {
  37. SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
  38. goto retry;
  39. }
  40. // Prevent a deadlock between the weak reference machinery
  41. // and the +initialize machinery by ensuring that no
  42. // weakly-referenced object has an un-+initialized isa.
  43. //防止弱引用机制之间的死锁
  44. //确保Class的初始化
  45. //为弱引用对象未初始化isa
  46. if (haveNew && newObj) {
  47. Class cls = newObj->getIsa();
  48. if (cls != previouslyInitializedClass &&
  49. !((objc_class *)cls)->isInitialized())
  50. {
  51. SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
  52. class_initialize(cls, (id)newObj);
  53. // If this class is finished with +initialize then we're good.
  54. // If this class is still running +initialize on this thread
  55. // (i.e. +initialize called storeWeak on an instance of itself)
  56. // then we may proceed but it will appear initializing and
  57. // not yet initialized to the check above.
  58. // Instead set previouslyInitializedClass to recognize it on retry.
  59. //赋值previouslyInitializedClass,防止重复初始化
  60. previouslyInitializedClass = cls;
  61. goto retry;
  62. }
  63. }
  64. // Clean up old value, if any.
  65. if (haveOld) {
  66. //存在旧值,将其移除
  67. weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
  68. }
  69. // Assign new value, if any.
  70. if (haveNew) {
  71. //调用weak_register_no_lock函数,传入当前对象所在散列表中的weak_table,并传入原始对象和弱引用对象指针
  72. newObj = (objc_object *)
  73. weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
  74. crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
  75. // weak_register_no_lock returns nil if weak store should be rejected
  76. // Set is-weakly-referenced bit in refcount table.
  77. if (!newObj->isTaggedPointerOrNil()) {
  78. newObj->setWeaklyReferenced_nolock();
  79. }
  80. // Do not set *location anywhere else. That would introduce a race.
  81. //将原始对象赋值给弱引用对象
  82. *location = (id)newObj;
  83. }
  84. else {
  85. // No new value. The storage is not changed.
  86. }
  87. SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
  88. // This must be called without the locks held, as it can invoke
  89. // arbitrary code. In particular, even if _setWeaklyReferenced
  90. // is not implemented, resolveInstanceMethod: may be, and may
  91. // call back into the weak reference machinery.
  92. callSetWeaklyReferenced((id)newObj);
  93. return (id)newObj;
  94. }
  • 将弱引用对象指针location和原始对象newObj函数

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

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

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

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

  • 判断haveNew存在

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

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

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

5.2.2 weak_register_no_lock

进入weak_register_no_lock函数

  1. id
  2. weak_register_no_lock(weak_table_t *weak_table, id referent_id,
  3. id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
  4. {
  5. //referent_id:原始对象
  6. //referrer_id:弱引用对象的指针
  7. objc_object *referent = (objc_object *)referent_id;
  8. objc_object **referrer = (objc_object **)referrer_id;
  9. if (referent->isTaggedPointerOrNil()) return referent_id;
  10. // ensure that the referenced object is viable
  11. //确保引用的对象是可用的
  12. if (deallocatingOptions == ReturnNilIfDeallocating ||
  13. deallocatingOptions == CrashIfDeallocating) {
  14. bool deallocating;
  15. if (!referent->ISA()->hasCustomRR()) {
  16. deallocating = referent->rootIsDeallocating();
  17. }
  18. else {
  19. // Use lookUpImpOrForward so we can avoid the assert in
  20. // class_getInstanceMethod, since we intentionally make this
  21. // callout with the lock held.
  22. auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
  23. lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
  24. referent->getIsa());
  25. if ((IMP)allowsWeakReference == _objc_msgForward) {
  26. return nil;
  27. }
  28. deallocating =
  29. ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
  30. }
  31. if (deallocating) {
  32. if (deallocatingOptions == CrashIfDeallocating) {
  33. _objc_fatal("Cannot form weak reference to instance (%p) of "
  34. "class %s. It is possible that this object was "
  35. "over-released, or is in the process of deallocation.",
  36. (void*)referent, object_getClassName((id)referent));
  37. } else {
  38. return nil;
  39. }
  40. }
  41. }
  42. // now remember it and where it is being stored
  43. weak_entry_t *entry;
  44. //判断在表中是否能找到对象的存储
  45. if ((entry = weak_entry_for_referent(weak_table, referent))) {
  46. //已存在,将弱引用对象追加到表中
  47. append_referrer(entry, referrer);
  48. }
  49. else {
  50. //不存在,创建weak_entry_t,将原始对象和弱引用对象关联
  51. weak_entry_t new_entry(referent, referrer);
  52. //重新开辟一张表
  53. weak_grow_maybe(weak_table);
  54. //将new_entry插入到表中
  55. weak_entry_insert(weak_table, &new_entry);
  56. }
  57. // Do not set *referrer. objc_storeWeak() requires that the
  58. // value not change.
  59. return referent_id;
  60. }
  • 传入原始对象referent_id和弱引用对象的指针referrer_id

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

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

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

    • 不存在

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

      • 重新开辟一张表

      • new_entry插入到表中

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

5.2.3 append_referrer

进入append_referrer函数

  1. static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
  2. {
  3. if (! entry->out_of_line()) {
  4. // Try to insert inline.
  5. //传入原始对象所属的weak_entry_t结构体指针和弱引用对象指针
  6. //找到weak_entry_t下的inline_referrers,一个对象可能被赋值给多个弱引用对象
  7. //循环找到空位,如果存在空位直接赋值
  8. for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
  9. if (entry->inline_referrers[i] == nil) {
  10. entry->inline_referrers[i] = new_referrer;
  11. return;
  12. }
  13. }
  14. // Couldn't insert inline. Allocate out of line.
  15. //没有空位,重新创建new_referrers
  16. weak_referrer_t *new_referrers = (weak_referrer_t *)
  17. calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
  18. // This constructed table is invalid, but grow_refs_and_insert
  19. // will fix it and rehash it.
  20. //将现有entry下的inline_referrers中的元素,循环导入到新创建的new_referrers中
  21. for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
  22. new_referrers[i] = entry->inline_referrers[i];
  23. }
  24. //相关成员变量的覆盖
  25. entry->referrers = new_referrers;
  26. entry->num_refs = WEAK_INLINE_COUNT;
  27. entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
  28. entry->mask = WEAK_INLINE_COUNT-1;
  29. entry->max_hash_displacement = 0;
  30. }
  31. ASSERT(entry->out_of_line());
  32. //扩容相关操作
  33. if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
  34. return grow_refs_and_insert(entry, new_referrer);
  35. }
  36. size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
  37. size_t index = begin;
  38. size_t hash_displacement = 0;
  39. while (entry->referrers[index] != nil) {
  40. hash_displacement++;
  41. index = (index+1) & entry->mask;
  42. if (index == begin) bad_weak_table(entry);
  43. }
  44. if (hash_displacement > entry->max_hash_displacement) {
  45. entry->max_hash_displacement = hash_displacement;
  46. }
  47. weak_referrer_t &ref = entry->referrers[index];
  48. ref = new_referrer;
  49. entry->num_refs++;
  50. }
  • 传入原始对象所属的weak_entry_t结构体指针和弱引用对象指针

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

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

  • 没有空位,重新创建new_referrers

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

  • 相关成员变量的覆盖

  • 扩容相关操作

5.4 weakObjc引用计数为2的原因

查看汇编代码:
image.png

  • 弱引用对象在使用时,会调用objc_loadWeakRetained函数
  • 汇编代码中的两次调用,第一次在执行CFGetRetainCount函数之前,第二次在执行NSLog方法之前

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

  1. id
  2. objc_loadWeakRetained(id *location)
  3. {
  4. id obj;
  5. id result;
  6. Class cls;
  7. SideTable *table;
  8. retry:
  9. // fixme std::atomic this load
  10. //对弱引用对象指针取值
  11. obj = *location;
  12. if (obj->isTaggedPointerOrNil()) return obj;
  13. table = &SideTables()[obj];
  14. table->lock();
  15. if (*location != obj) {
  16. table->unlock();
  17. goto retry;
  18. }
  19. //赋值给result临时变量
  20. result = obj;
  21. cls = obj->ISA();
  22. if (! cls->hasCustomRR()) {
  23. // Fast case. We know +initialize is complete because
  24. // default-RR can never be set before then.
  25. ASSERT(cls->isInitialized());
  26. //调用retain函数,对引用计数+1
  27. if (! obj->rootTryRetain()) {
  28. result = nil;
  29. }
  30. }
  31. else {
  32. // Slow case. We must check for +initialize and call it outside
  33. // the lock if necessary in order to avoid deadlocks.
  34. // Use lookUpImpOrForward so we can avoid the assert in
  35. // class_getInstanceMethod, since we intentionally make this
  36. // callout with the lock held.
  37. if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
  38. BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
  39. lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
  40. if ((IMP)tryRetain == _objc_msgForward) {
  41. result = nil;
  42. }
  43. else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
  44. result = nil;
  45. }
  46. }
  47. else {
  48. table->unlock();
  49. class_initialize(cls, obj);
  50. goto retry;
  51. }
  52. }
  53. table->unlock();
  54. return result;
  55. }
  • 传入弱引用对象指针
  • 对弱引用对象指针取值,赋值给obj,相当于原始对象
  • obj赋值给result,此时引用计数不变
  • 核心代码:if (! obj->rootTryRetain()),调用obj调用retain函数,引用计数+1

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

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

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

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

5.5 weak流程图

image.png

总结

内存布局:

  • 当程序运行时,系统会开辟内核区、程序使用的内存五大区和保留区

  • 内存五大区,主要分为栈区、堆区、全局区、常量区、代码区

内存管理方案:

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

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

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

  • retain操作,如果对象isaNonpointerISA,当extra_rc存储已满,将extra_rc的一半存储到SideTables

  • release操作,如果extra_rc减空,从SideTables中读取出一半,存储到extra_rc

  • SideTable本质就是一张哈希表,底层使用StripedMap。根据不同系统架构,可创建864张。在iOS设置上,只能创建8

  • SideTable结构中,包含一把锁、一张引用计数表和一张弱引用表

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

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

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

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

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

    • 因为对SideTable的操作,需要经过加锁、解锁保证线程安全,相对耗时。如果extra_rc存满后全部导入SideTable中,在引用计数-1的时候,需要频繁对SideTable进行操作,效率太低
  • 所属空间中并不是直接存储引用计数,而是使用位域存储很多信息。真正的refcnt在位域中的第2位上存储,所以每次+1-1的时候,需要进行1<<2的处理,目的就是不影响前面两位

WeakTable

  • SideTable下的弱引用表,SideTable本身有多张表,通过对象可以找到所属的SideTable

  • SideTable下存储weak_table_t,它是一个结构体,里面存储了weak_entry_t结构的另一张表

  • weak_entry_t表中存储原始对象referent,以及一个weak_referrer_t结构的列表inline_referrers

  • inline_referrers中存储了弱引用对象的指针,因为一个对象可能被赋值给多个弱引用对象,所以使用列表存储

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

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

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

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