这篇文章继续上一篇Block深入浅出 (一)讲解block的存储和copy问题
三 Block的存储和copy操作
block的三种类型
-
全局Block(_NSConcreteGlobalBlock)
-
栈Block (_NSConcreteStackBlock)
-
堆Block (_NSConcreteMallocBlock)
这三种block在内存的存储区域如下图:
1.png
-
全局Block存在于data段,相当于单例
-
栈Block存在于栈区,超出作用域立即销毁,由系统管理
-
堆Block存在于堆区,是一个带引用计数的对象,需要程序员自己管理其内存
遇到一个Block,我们怎么知道这个Block的存储位置呢(只讨论ARC下)
- block不访问外部变量(包括栈中和堆中的变量),此block即不在栈中也不在堆中,此时为全局Block
- 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到了堆区,那么访问该变量是访问栈上还是堆上的呢?

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