在Block前言中,讲到Block 的isa指针六种类型,以及每种类型的存储区域。简单回顾一下最终结论
类型 | 查看源 | 存储区域 |
---|---|---|
_NSConcreteGlobalBlock | .cpp文件/Block.h | 全局变量/静态变量区 |
_NSConcreteStackBlock | .cpp文件/Block.h | 栈区 |
_NSConcreteMallocBlock | Block_private.h文件 | 堆区 |
_NSConcreteAutoBlock | Block_private.h文件 | 堆区 |
_NSConcreteFinalizingBlock | Block_private.h文件 | 堆区 |
_NSConcreteWeakBlockVariable | Block_private.h文件 | 堆区 |
以上内容的查看在编译后的cpp文件以及runtime源码中都可以查看到相关信息
code已上传到Github,点击下载
runtime源码Apple官网
runtime源码Github
特别注意点
Block前言中讲到默认创建的Block指针只有Global、Statck,其它四种是在运行时编译环境决定的,此根据也是根据runtime源码和注释得出结论。
// the raw data space for runtime classes for blocks
// class+meta used for stack, malloc, and collectable based blocks
BLOCK_EXPORT void * _NSConcreteMallocBlock[32]
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
从以上runtime源码和注释中我们可以很好的解释该6种Block在Mac和iOS系统中都会出现,除Global、Statck,其它四种是在运行时编译环境决定。
每种Block出现的情况
(最好复习一下基本数据结构)
_NSConcreteGlobalBlock
在以下情况下,Block为GlobalBlock (根据赋值情况会决定存储静态区的静态变量区域还是全局变量区域)
1.Block声明为全局变量
2.函数区域内Block语法表达式没有使用外部变量
3.Block内部只引用了内部传递值或只引用了静态变量或全局变量
第一种情况好理解,一般我们声明的全局变量都会放在静态区,当Block被声明为全局变量时也会存放在静态区域
如下将Block声明为全局变量,查看编译后的cpp文件,找到对应的编译代码
源码:
#import "BlockObject.h"
#import <objc/runtime.h>
void (^globalBlock)(void)=^{};
@interface BlockObject()
编译后的部分代码:
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
`impl.isa = &_NSConcreteGlobalBlock;可以看到我们声明的全局变量block为NSConcreteGlobalBlock。
第二种情况我们可以看如下代码
typedef int (^blockStatic)(void);
blockStatic secondBlk = ^(){
return 1;
};
NSLog(@"%@",secondBlk);
最后查看到输出结果如下
YAObjectTest[8834:1196308]
<__NSGlobalBlock__: 0x10de381c8>
所以,未引用外部变量时,最终得到的block也是NSConcreteGlobalBlock。
第三种情况我看可以看如下代码
static int count = 100;
typedef int (^blockStatic)(void);
blockStatic blk = ^(){
return count;
};
NSLog(@"%@",blk);
最后的输出结果如下:
YAObjectTest[8719:1185296]
<__NSGlobalBlock__: 0x108320188>
最后的输出也是GlobalBlock,所以,只引用静态变量或全局变量时,Block仍为NSConcreteGlobalBlock。
解惑:第二种情况和第三种情况我们最终查看的是输出情况,而不是clang后的源文件。查看clang后的源文件该两种情况得到的是StackBlock。编译器根据编译特性会把后两种情况默认编译后定义为StackBlock,但是我们知道OC是动态语言,所以最终还是以运行时或最终输出结果为准。
_NSConcreteStackBlock
1.使用到外部局部变量、成员属性变量(非静态变量值)& 2.未使用strong或copy修饰符修饰
以上条件缺一不可,只有这两个条件同时成立时,Block才为_NSConcreteStackBlock
如果在函数内,Block只是引用了外部局部变量或成员属性变量,最终会被copy到堆上变为MallocBlock。而对于声明为属性的Block,如果修饰符为strong或copy,则也会copy到堆上,而不是StackBlock。
查看以下代码的最终输出(不能同时满足上述两种条件的情况下)
@interface BlockObject()
@property (nonatomic, strong)void(^proBlock)(void);
@property (nonatomic, assign)NSInteger outsideCount;
@end
int a = 1;
//使用外部局部变量情况
void (^blockVariable)(void) = ^(){
NSLog(@"%ld",(long)_outsideCount);
};
NSLog(@"%@",blockVariable);
_proBlock = ^(){
NSLog(@"%d",a);
};
NSLog(@"%@",_proBlock);
最终控制台输出如下
<__NSMallocBlock__: 0x600000447c50>
<__NSMallocBlock__: 0x6040002557b0>
将proBlock的修饰符换成weak,
@property (nonatomic, weak)void(^proBlock)(void);
同样的代码,会得到如下结果,proBlock会是StackBlock
YAObjectTest[10461:1384618] <__NSMallocBlock__: 0x60400025d1f0>
YAObjectTest[10461:1384618] <__NSStackBlock__: 0x7ffee9f07930>
所以,在引用了外部变量并且没有强指针引用的情况下的Block为_NSConcreteStackBlock。
_NSConcreteMallocBlock
1.引用了外部变量,有strong或copy修饰符修饰
2.block内部需要修改引用变量的值,外部变量被block修饰
第一种情况在求证StackBlock的情况下已经得到验证,有strong或copy修饰后并且引用了外部变量的情况下,为** <NSMallocBlock__: 0x6040002557b0>**
有关第二种情况,在该文只做验证,后续深入解析,详见Block截获变量中的__block修饰符深入解析
__block int a = 0;
void (^lockBlock)(void) = ^{
a++;
};
lockBlock();
NSLog(@"%@", lockBlock);
最后的输出结果为
YAObjectTest[10853:1425358] <__NSMallocBlock__: 0x60000024e610>
在此就求证了以上两种结果最后Block为_NSConcreteMallocBlock。
接下来的三种类型Block,因对有GC回收机制的语言不是太熟悉,在Xcode中编写的C++代码 最终查看运行时的isa指针皆为—NSStackBlock,在Xcode中并没有按照预想的查看到以下三种类型,Terminal终端g++ 编译后的可执行文件也未见以下类型,查阅资料发现以下三种出现的情况大致如下,若有对C++ 熟悉的人员 ,请不吝赐教😉
_NSConcreteFinalizingBlock
.Block需要copy到堆上,但是Block内部有ctors 和 dtors时,block会是NSFinalizingBlock
_NSConcreteAutoBlock
.Block需要copy到堆上若未引用到ctors和 dtors 则是NSAutoBlock
以上是AutoBlock和FinalizingBlock的出现情况。而对于ctors和dtors,
ctors中保存着程序全部构造函数的指针数组,dtors中保存着程序全部析构函数的指针数组,从两者的存储内容来看,若block引用了该两种类型,势必block会在程序运行结束时回收内存,所以会被转换为FinalizingBlock,而未引用的block则会根据情况自动回收,转换为AutoBlock。
以下是写在cpp文件中的代码
void testAutoBlock(){
int * b=new int[4];
__block int testCount = 100;
int (^myBlock)() = ^() {
b[0] = testCount;
b[1] = testCount + 1;
b[2] = testCount + 2;
b[3] = testCount + 3;
std::cout<<b<<std::endl;
return 0;
};
std::cout<<myBlock<<std::endl;
};
_NSConcreteWeakBlockVariable
.GC回收机制下,用_weak 或block修饰的block 会转变成NSWeakBlockVariable
具体的对于weak 和__blcok修饰符,在后面截获变量和循环引用中具体详解。
以上是个人对于Block类型和存储区域的总结。中间为探究后三种类型浪费了很多时间,导致文章延迟。