内存管理、自动释放池与循环引用

引用自: 内存管理、自动释放池与循环引用

一、内存布局

1. 堆栈解释

  • 栈(stack): 方法调用, 局部变量等, 是连续的, 高地址(0xc0000000)往低地址(0xc8048000)扩展
  • 堆(heap): 通过alloc等分配的对象, 是离散的, 低地址往高地址扩展, 需要手动控制
  • 未初始化数据(bss): 未初始化的全局变量等
  • 已初始化数据(data): 已初始化的全局变量等
  • 代码段(text): 程序代码

2. 64bit和32bit下long 和 char所占字节是不同的

char: 1字节(ASCII 2⁸ = 256个字符)
char*(即指针变量): 4个字节(32位的寻址控件是2³², 即32个bit,也就是4个字节. 同理64位编译器为8个字节)
short int: 2个字节范围 -2¹⁶~>2¹⁶即 -32768~>32767
int: 4个字节 范围 -2147483648 ~> 2147483647
unsigned int: :4个字节
long: 4个字节 范围和int 一样64位下8个字节, 范围-9223372036854775808 ~ 9223372036854775807
long long: 8个字节 范围 -9223372036854775808 ~ 9223372036854775807
unsigned long long: 8个字节 最大值: 184467440737095516
float: 4个字节
double: 8个字节

二、内存管理方案

  • taggedPointer: 储存小对象如NSNumber.
  • NONPOINTER_ISA(非指针型的isa): 在64位结构下, isa指针是占64比特位的, 实际上只有30多位就已经够用了, 为了提高利用率, 剩余的比特位储存了内存管理的相关数据内容.
  • 散列表: 复杂的数据结构, 包括了引用计数表和弱引用表
    通过SideTables()结构来实现的, SideTables()结构下, 有很多SideTable的数据结构.
    而sideTable当中包含了自旋锁, 引用计数表, 弱引用表.
    SideTables()实际上市一个哈希表, 通过对象的地址来计算该对象的引用计数在哪个sideTable中

自旋锁:

  • 自旋锁是"忙等"的锁
  • 适用于轻量访问

引用计数表和弱引用表实际是一个哈希表,来提高查找效率

三、MRC(手动引用计数) 和ARC(自动引用计数)

1. MRC: alloc, retain, release, retainCount, autorelease, dealloc
2. ARC:

  • ARC是LLVM和Runtime协作的结果
  • ARC进制手动调用retain, release, retainCount, autorelease关键字
  • ARC新增weak, strong关键字

3. 引用计数管理:

  • alloc: 经过一系列函数调用, 最终调用了calloc函数, 这里并没有设置引用计数为1
  • retain: 经过两次哈希查找, 找到其对应引用计数值, 然后将引用计数加1(实际是加偏移量)
  • release: 和retain相反, 经过两次哈希查找, 找到其对应引用计数值, 然后将引用计数减1
  • dealloc:

4.弱引用管理

  • 添加weak变量:通过哈希算法位置查找添加. 如果查找对应位置中已经有了当前对象所对应的弱引用数组, 就把新的弱引用变量添加到数组当中; 如果没有, 就创建一个弱引用数组, 并将该弱引用变量添加到该数组中.
  • 当一个被weak修饰的对象被释放后, weak对象怎么处理?
    清楚weak变量,同时设置指向为nil. 当对象被dealloc释放后, 在dealloc的内部实现中, 会电泳弱引用清除的相关函数, 会根据当前对象指针查找弱引用表, 找到当前对象所对应的弱引用数组,将数组中的所有弱引用的指针都设置为nil.

5.自动释放池
在档次runloop将要结束的时候调用objc_autoreleasePoolPop, 并push进来一个新的AutoreleasePool

AutoreleasePoolPage是以栈为节点通过双向链表的形式组合而成, 是和线程一一对应的. 内部属性有parent,child对应前后两个节点,thread对应线程, next指针指向栈中下一个可填充的位置.

  • AutoreleasePool实现原理?
    编译器会将@autoreleasepool{}改写为:
void * ctx = objc_autoreleasePoolRush;
    {}
objc_autoreleasePoolPop(ctx);
  • objc_autoreleasePoolPop:
    根据传入的哨兵对象找到对应位置.
    给上次push操作之后添加的对象一次发送release消息.
    回退next指针到正确位的位置.

四、循环引用

循环引用的实质: 多个对象相互之间有强引用,不能释放让系统回收.
如何解决循环引用?

  1. 避免产生循环引用, 通常是将strong引用改为weak引用.
    比如在修饰属性时用weak
    在block内调用对象方法时, 使用其弱引用, 这里可以使用两个宏
#define WS(weakSelf)                        __weak __typeof(&*self)weakSelf = self;
#define ST(strongSelf)                      __strong __typeof(&*self)strongSelf = weakSelf;

还可以使用__block来修饰变量
在MRC下,__block不会增加引用计数, 避免了循环引用
在ARC下,__block修饰对象会被强引用,无法避免循环引用, 需要手动解除

2.在适合时机去手动断开循环引用.
通常我们使用第一种.
循环引用场景:

  • 自循环引用
    对象强持有的属性同时持有该对象
  • 相互循环引用
  • 多循环引用
    1. 代理(delegate)循环引用属于相互循环引用
    delegate是iOS开发中比较常遇到的循环引用, 一般在声明delegate的时候都要使用弱引用weak,或者assign,当然怎么选择使用assign还是weak, MRC的话只能用assign, 在ARC的情况下最好使用weak, 因为weak修饰的变量在释放后自动执行nil,防止野指针存在
    2. NSTimer循环引用属于相互循环使用
    在控制器内, 创建NSTimer作为其属性, 由于定时器创建后也会强引用改控制器对象, 那么该对象和定时器就相互循环引用了.
    3. block循环引用
    一个简单的例子:
@property(copy, nonatomic) dispatch_block_t myBlock;
@property(copy, nonatomic) NSString *blockString;

- (void)testBlock
{
    self.myBloc = ^()
    {
        NSLog(@"%@",self.blockString);
    };
}

由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用.
解决方案就是使用__weak修饰self即可

__weak typeof(self) weakSelf = self;

self.myBlock = ^()
{
        NSLog(@"%@",weakSelf.blockString);
}
  • 并不是所有block都会造成循环引用.
    只有被强引用了的block才会产生循环引用
    而比如下边这些系统方法等
dispatch_async(dispatch_get_main_queue(),^{
      [UIView animateWithDuration:1 animations:^{
      
      }]
}),

或者block并不是其属性而是临时变量,即栈block

[self testWithBlock:^{
        NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block
{
    block();
}

还有一种场景, 在block执行开始时self对象还未被脂肪, 而执行过程中,self被释放了, 由于是用weak修饰的,那么weakSelf也被释放了,此时在block里访问weakSelf时,就可能会发生错误(向nil对象发消息并不会崩溃,但也没任何效果).
对于这种场景,应该在block中对 对象使用__strong修饰,使得在block期间对 对象持有, block执行结束后, 解除其持有.

__weak typeof(self) weakSelf = self;
self.myBlock = ^()
{
        __strong __typeof(self) strongSelf = weakSelf;
        [strongSelf test];
}

你可能感兴趣的:(内存管理、自动释放池与循环引用)