zl程序教程

您现在的位置是:首页 >  其他

当前栏目

iOS中block块的存储位置&内存管理

2023-03-15 23:22:34 时间

block是iOS开发中一种使用方便的代码块,但是在使用过程中也很容易不小心就造成问题,本文讲解其存储位置所决定的内存修饰以及如何避免循环引用。

iOS内存分区

先讲讲大的,关于iOS在内存中的分区情况。

内存分为五个区:栈区、堆区、全局区、常量区、代码区。这五个区在物理上是分开的,如下图所示:

这五个区存储的内容也各有划分:

  • 栈区(stack):这一块区域系统会自己进行管理,我们不用干预,主要存一些局部变量,以及函数跳转时的现场保护。因此大量的局部变量、深递归、函数循环调用都可能耗尽内存而造成运行崩溃。
  • 堆区(heap):与栈区相对,这一块一般由我们开发人员管理,比如一些alloc、free的操作,存储一些自己创建的对象。
  • 全局区(静态区 static):全局变量和静态变量都存储在这里,已经初始化的和没有初始化的变量会分开存储在相邻的区域,程序结束后系统来释放。
  • 常量区:存储常量字符串和const常量。
  • 代码区:顾名思义,就是存我们写的代码。

block块存储位置

block块根据情况有两种可能的存储位置,一种存在代码区,一种存在堆区。

1、如果block块没有访问处于栈区的变量(比如局部变量),也没有访问堆区的变量(比如我们alloc创建的对象),那就存在代码区,即使访问了全局变量,也依然存在代码区。

2、如果访问了栈区或者堆区的变量,那就会被存在堆区(实际存在栈区,ARC下会自动拷贝到堆区)。

关于存在堆区的情况,有一点需要注意的是,堆区是不断变化的,不断地有变量的创建和销毁,如果block块没有强引用,那也随时可能被销毁,这就导致一旦在销毁时访问block块,程序就会崩溃,所以,在定义block时,内存修饰最好用strong或者copy。而且在使用时也最好先判断一下block是否为空,比如:

if (!block) {
    return;
}
block();

循环引用

既然在修饰block时,使用了strong,那么另一个问题就需要注意了,也就是循环引用。

当使用了strong修饰后,self会强引用block,而如果在block中又需要访问self的一些属性或者方法,从而调用了self,这时self和block就进入循环引用,容易内存溢出。

解决的办法时在block中的需要用到self时,事先将self用__weak修饰,这样互相引用的一方就不再是强引用了。

比如:

__weak ViewController *weakSelf = self;
self.block = ^{
    weakSelf.str = @"123";
};

但是这样还不够,在多线程下,单单使用weakSelf,可能前一刻weakSelf还在,后面需要用时却被释放掉了,毕竟弱引用是不稳定的,这时候就需要又使用一个修饰符__strong来在block中修饰,是不是操碎了心。

因此更好的释放方式如下:

__weak __typeof(self) *weakSelf = self;
self.block = ^{
    __strong __typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        strongSelf.str = @"123";
    }
    // 如果不用了,应置为空
    strongSelf.block = nil;
};

平常使用block的情况很多,很多人往往都是直接拿样例代码改着用了,不知道为什么要这么修饰block,也不知道weakSelf、strongSelf有什么用。这里就从存储位置来解释为什么要这样修饰block,从而又会造成循环引用的问题,最后如何去解决他。希望可以帮助大家更好的理解手中的每一行代码。