RunTime源码阅读(四)内存管理

这一章节拿来做笔记,加深理解的,更详细可参考iOS 内存管理
唯一不同的,借用了计算器来验证了原文的理论。

1.内存分区

内核区、栈区(由高到低)、堆区(由低到高)、未初始化数据(.bss)、已初始化数据(.data)、代码段(.text)、保留

  1. Tagged Pointer
    专门用来存储小的对象,例如NSNumber, NSDate, NSString。
    Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
    在字符串长度在9个以内时,iOS其实使用了tagged pointer做了优化的。
  NSMutableString *mutableStr = [NSMutableString string];
  NSString *immutable = nil;
  #define _OBJC_TAG_MASK (1UL<<63)
  char c = 'a';
  do {
      [mutableStr appendFormat:@"%c", c++];
      immutable = [mutableStr copy];
      NSLog(@"%p %@ %@", immutable, immutable, immutable.class);
  }while(((uintptr_t)immutable & _OBJC_TAG_MASK) == _OBJC_TAG_MASK);

RunTime源码阅读(四)内存管理_第1张图片
taggedPointer与string

在arm64中,当字符超过9个时,使用的是__NSCFString存储;否则是NSTaggedPointerString存储。

  1. isa 指针(NONPOINTER_ISA)
    和对象引用计数相关的有两个成员:extra_rc和has_sidetable_rc。iOS用19位的extra_rc来记录对象的引用次数,当extra_rc 不够用时,还会借助sidetable来存储计数值,这时,has_sidetable_rc会被标志为1。

标志位:
nonpointer 1bit 标志位。1(奇数)表示开启了isa优化,0(偶数)表示没有启用isa优化。所以,我们可以通过判断isa是否为奇数来判断对象是否启用了isa优化。
has_assoc 1bit 标志位。表明对象是否有关联对象。没有关联对象的对象释放的更快。
has_cxx_dtor 1bit 标志位。表明对象是否有C++或ARC析构函数。没有析构函数的对象释放的更快。
shiftcls 33bit 类指针的非零位。
magic 6bit 固定为0x1a,用于在调试时区分对象是否已经初始化。
weakly_referenced 1bit 标志位。用于表示该对象是否被别的对象弱引用。没有被弱引用的对象释放的更快。
deallocating 1bit 标志位。用于表示该对象是否正在被释放。
has_sidetable_rc 1bit 标志位。用于标识是否当前的引用计数过大,无法在isa中存储,而需要借用sidetable来存储。(这种情况大多不会发生)
extra_rc 19bit 对象的引用计数减1。比如,一个object对象的引用计数为7,则此时extra_rc的值为6。

RunTime源码阅读(四)内存管理_第2张图片
isa指针的结构

至此我们可以验证当一个对象有关联对象、weak及引用计数。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    MyObj *obj = [[MyObj alloc] init];
    NSLog(@"1. obj isa_t = %p", *(void **)(__bridge void*)obj);
    _obj1 = obj;
    MyObj *tmpObj = obj;
    NSLog(@"2. obj isa_t = %p", *(void **)(__bridge void*)obj);
    
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"3. obj isa_t = %p", *(void **)(__bridge void*)_obj1);
    _obj2 = _obj1;
    NSLog(@"4. obj isa_t = %p", *(void **)(__bridge void*)_obj1);
    _weakRefObj = _obj1;
    NSLog(@"5. obj isa_t = %p", *(void **)(__bridge void*)_obj1);
    NSObject *attachObj = [[NSObject alloc] init];
    objc_setAssociatedObject(_obj1, "attachKey", attachObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSLog(@"6. obj isa_t = %p", *(void **)(__bridge void*)_obj1);
}
必须用真机,结果:
1. obj isa_t = 0x1a10547b411
 2. obj isa_t = 0x41a10547b411
 3. obj isa_t = 0x1a10547b411
 4. obj isa_t = 0x21a10547b411
 5. obj isa_t = 0x25a10547b411
 6. obj isa_t = 0x25a10547b413

MyObj就是一个普通的类。打印指向对象的指针。


打开Mac计算器,使用编程器

2.1 0x1a10547b411


RunTime源码阅读(四)内存管理_第3张图片
0x1a10547b411

0-63表示arm64位
第0位表示开启isa指针优化,是1;如果是模拟器则不会。
第1位表示关联对象has_assoc,为0
第42位表示weakly,为0
第45到63表示引用计数,注意到extra_rc此时为0,因为引用计数等于extra_rc + 1,因此,obj进行了alloc,引用计数为1,和我们的预期一致。

2.2 0x41a10547b411


RunTime源码阅读(四)内存管理_第4张图片
0x41a10547b411

第1位表示关联对象has_assoc,为0
第42位表示weakly,为0
第45到63表示引用计数,注意到extra_rc=2,因为引用计数等于extra_rc + 1=3,因此,_obj1引用,tmpObj也引用,即计数是3,和我们的预期一致。

2.3 0x1a10547b411


RunTime源码阅读(四)内存管理_第5张图片
0x1a10547b411

第1位表示关联对象has_assoc,为0
第42位表示weakly,为0
第45到63表示引用计数,注意到extra_rc=0,因为引用计数等于extra_rc + 1=1,程序执行到了viewDidAppear方法,因为此时栈上变量obj ,tmpObj已经释放,因此引用计数应该减2,等于1。和我们的预期一致。

2.4 0x21a10547b411


RunTime源码阅读(四)内存管理_第6张图片
0x21a10547b411

第1位表示关联对象has_assoc,为0
第42位表示weakly,为0
第45到63表示引用计数,注意到extra_rc=1,因为引用计数等于extra_rc + 1=2,此时_objc2也持有了_objc1,即计数是2。和我们的预期一致。

2.5 0x25a10547b411


RunTime源码阅读(四)内存管理_第7张图片
0x25a10547b411

第1位表示关联对象has_assoc,为0
第42位表示weakly,为1
第45到63表示引用计数,计数还是2.
_weakRefObj = _obj1;没有改变本来的引用计数。但第42位标志位发生变化。
和我们的预期一致。

2.6 0x25a10547b413


RunTime源码阅读(四)内存管理_第8张图片
0x25a10547b413

第1位表示关联对象has_assoc,为1
第42位表示weakly,为0
第45到63表示引用计数,计数还是xtra_rc + 1=2.
objc_setAssociatedObject不会改变引用计数。但第1位标志位发生变化。其他位保持不变。
和我们的预期一致。

  1. SideTable
    只有当extra_rc的二进制位不够用时,才会借助sideTable存储计数。同时SideTable也会管理weak的存储。

你可能感兴趣的:(RunTime源码阅读(四)内存管理)