Block深入浅出 (二)

这篇文章继续上一篇Block深入浅出 (一)讲解block的存储和copy问题

三 Block的存储和copy操作

block的三种类型
  • 全局Block(_NSConcreteGlobalBlock)

  • 栈Block (_NSConcreteStackBlock)

  • 堆Block (_NSConcreteMallocBlock)

    这三种block在内存的存储区域如下图:
    1.png
  • 全局Block存在于data段,相当于单例

  • 栈Block存在于栈区,超出作用域立即销毁,由系统管理

  • 堆Block存在于堆区,是一个带引用计数的对象,需要程序员自己管理其内存

遇到一个Block,我们怎么知道这个Block的存储位置呢(只讨论ARC下)
  1. block不访问外部变量(包括栈中和堆中的变量),此block即不在栈中也不在堆中,此时为全局Block
  2. block访问外部变量默认存储在堆区(实际是存在于栈区,但ARC情况下系统又自动拷贝到了堆区),自动释放。
ARC下,访问外部变量的block为什么要自动从栈区拷贝到了堆区呢?

栈上的block,如果其所属的变量作用域结束,该block被废弃,如同一般的自动变量,同时block中的__block变量也同时被废弃。所以为了解决超过作用域就被释放的问题,我们需要把block复制到堆区,延长其生命周期。在ARC下,大多数情况下编译器会恰当的进行判断是否需要将block从栈复制到堆,如果需要,编译器会自动生成将block从栈复制到堆区的代码。block的复制操作执行的是copy实例方法,block只要调用了copy方法,栈block就会变成堆block。

例如下面的一个返回值为block类型的方法

- (UILabel*(^)(NSString*))TM_Text {
    UILabel*(^block)(NSString*) = ^(NSString* name){
        self.text = name;
        return self;
    };
    return block;
}
  • 上面的方法返回的block默认是存在于栈上,所以方法执行完毕之后,block的作用域就结束了,block会被废弃,但在ARC下有效,这种情况编辑器会自动对block做一次copy处理。
  • 由于将block从栈上复制到堆上相当消耗CPU,所以当block设置在栈上也够使用时,就不要复制了,因为此时的复制只是在消耗CPU资源。

不同类型的block执行copy方法的效果图如下:

block 存储区域 执行copy之后的效果
全局Block 全局data段 什么都不做
栈Block 栈区 从栈区复制到堆区
堆Block 堆区 引用计数增加

所以在ARC下,不管是何种block,用copy方法都不会引起任何问题,在不确定时调用copy方法即可。

__block变量与__forwarding

在上一篇文章我们知道使用__block修饰的int变量变成了__Block_byref_a_0的结构体类型,想要访问变量本身的值需要使用该结构体中__forwarding指针指向的地址值。那在对block进行copy操作之后,__block修饰的变量也被copy到了堆区,那么访问该变量是访问栈上还是堆上的呢?


2.png

通过上图可以知道原本栈上__block修饰的变量的__forwarding指针指向自己,而block进行copy操作之后,__block修饰的变量也copy了一份到堆区,并且堆区的此变量的__forwarding指向自己,而栈上的此变量的__forwarding指向了堆区的变量,这样就实现了不管是该变量在堆区还是栈区,也不管是在block内部还是外部访问该变量,都能顺利的访问同一个__block变量。

你可能感兴趣的:(Block深入浅出 (二))