本文主要讨论block的存储域与__block变量的存储域(即存储位置)
当引用了OC中的Foundation或者UIKit框架时,通过 clang -rewrite-objc 指定文件名 命令将指定文件转换成C++代码会报错,可通过 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 指定文件名
我们知道它们在转换成C++源代码时都被转换成了结构体类型,都是在栈上生成的结构体实例
名称 | 实质 |
---|---|
block | 栈上block的结构体实例 |
__block变量 | 栈上__block变量的结构体实例 |
前面我们说到block其实也是一种OC对象,其对应的类以及存储位置有以下几种
类(block的isa类型) | 存储位置 |
---|---|
NSConcreteGlobalBlock | 程序的数据区域(全局静态区) |
NSConcreteMallocBlock | 堆上 |
NSConcreteStackBlock | 栈上 |
之前我们遇到的都是NSConcreteStackBlock类的block,一旦我们在全局区声明一个block时,例如
#import <stdio.h>
void (^blk) (void) = ^{
};
int main() {
return 0;
}
将此代码转换成C++源码时,就会发现其isa指向的是NSConcreteGlobalBlock,该block在全局区,不能引用自动变量,所以不存在截获自动变量的情况,结构体实例内容不依赖执行时的状态,整个程序运行过程中只需要一个实例,所以与全局变量一样存储在数据区域(全局静态区)。
总结如下:
1、在全局区域声明定义一个block
2、block表达式中没有使用捕获的自动变量时
以上情况生成的block都是NSConcreteGlobalBlock类型,只生成一个结构体实例;除此情况下,生成的都是NSConcreteStackBlock类型的block,且都是存储在栈上。那么block配置在堆上的NSConcreteMallocBlock类什么时候使用呢?
block存储域
1、block超出变量作用域可存在的原因
2、__block变量中的结构体成员变量__forwarding存在的原因
配置在全局的block在变量作用域可以通过指针安全使用。但是设置在栈上的block如果所属的变量作用域结束,该block就被废弃。同理,__block变量也配置在栈上,如果所属的变量作用域结束,该变量也会被废弃。那么block是如何解决这个问题的呢?
block通过将block和__block变量从栈上复制到堆上的方法来解决这个问题
,即使变量作用域结束了,堆上的block或__block变量还 可以继续存在。
- 复制到堆上的block将结构体实例中的成员变量isa设置为_NSConcreteMallocBlock
- __block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上都能够正确的访问__block变量
block提供的复制方法是:objc_retainBlock函数,实际上就是_Block_copy函数
在大多数情况下,编译器能自行判断,自动将block从栈上复制到堆上。
需要我们使用copy实例方法手动生成代码将block从栈上复制到堆上,主要针对下面这种情况:
当方法或函数的参数中传递的是block时
如果在方法或者函数中复制了传递过来的参数,就无须手动复制了,例如:
- Cocoa框架中的方法且方法名中含有usingBlock等时
- GCD中的API
block的类 | 副本源的配置存储位置 | copy复制效果 |
---|---|---|
NSConcreteGlobalBlock | 程序的数据区域(全局静态区) | 什么也不做 |
NSConcreteMallocBlock | 堆 | 引用计数加1 |
NSConcreteStackBlock | 栈 | 从栈复制到堆 |
__block变量存储域(存储位置)
当使用了__block变量的block从栈上复制到堆上时,__block变量也会受到影响
__block变量的配置存储位置 | block从栈复制到堆上的影响 |
---|---|
栈 | 从栈复制到堆并被block持有 |
堆 | 被block持有 |
当多个block使用__block变量时,任何一个block从栈复制到堆时,__block变量也会一并从栈复制到堆上并被该block持有,剩下的block从栈复制到堆时,也会持有该__block变量,并增加变量的引用计数,与OC的引用计数式内存管理思想一致。如果所有持有__block的block都被废弃,__block变量也就会被释放。
通过block的复制,__block变量也从栈复制到堆,此时可同时访问栈上的__block变量和堆上的__block变量
栈上的__block变量的结构体实例在__block变量从栈复制到堆上时,会将成员变量__forwarding的值替换为复制目标堆上的__block变量的结构体实例地址。
又见block(一):block是什么?
又见block(二):block语法定义
又见block(三):block实质
又见block(四):block捕获自动变量
又见block(五): __block变量和对象
又见block(七):截获对象