美文网首页我依然爱iOS
iOS中block块的存储位置&内存管理

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

作者: Cloudox_ | 来源:发表于2017-11-27 10:34 被阅读8次

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

    iOS内存分区

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

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

    image.png

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

    • 栈区(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,从而又会造成循环引用的问题,最后如何去解决他。希望可以帮助大家更好的理解手中的每一行代码。


    查看作者首页

    相关文章

      网友评论

        本文标题:iOS中block块的存储位置&内存管理

        本文链接:https://www.haomeiwen.com/subject/mgurbxtx.html