1.Block的类型
根据Block的内存地址,系统把Block分为3类:NSGlobalBlock,NSStackBlock, NSMallocBlock;
NSGlobalBlock :全局block,位于内存全局区
NSMallocBlock :堆区block,位于内存堆区
NSStackBlock :栈区block,位于内存栈区
而block内存在哪里,主要是看block内部是否引用了局部变量,我们来看下面的输出例子
从输出的例子中我们可以看到,如果block内部没有引用局部变量,那么block就是一个全局的block,存放位置就是在全局区,如果block引用了局部变量,那么block就一个是堆区block,存放的位置是堆区。其实上面的block在声明的时候其实默认隐藏了参数,默认参数是__strong ,对应的是__weak,在ARC环境下,如果用__weak修饰的block,就会生成栈区block(NSStackBlock),当然,如果说block内部没有引用外部变量,那么它还是全局的block,比如说下面的例子:
有局部变量就有全局变量,如果说block内部引用了全局变量,那block是什么类型?我们继续看输出
由此可见,block是什么类型,主要是由两个因素决定的,一个是是否引用了局部变量,一个是是否是使用strong修饰的,ARC环境下strong修饰过的block会被拷贝到堆区,变成堆区block,所以MRC环境下有三种block,但是ARC环境下只有两种block,NSStackBlock在创建的时候就会被拷贝到堆区。
2.Block的内存管理
block的内存管理分为两类,一类是block本身的内存管理,一个是block引用的变量的内存管理,我们先来理解block本身的内存管理。
对于Block,系统专门提供了两个函数用来处理block的拷贝和释放 分别是:
Block_copy相当于的copy, Block_release相当于release,虽然有copy和release,但是不同类型的block,这两个方法会有不同的表现:
NSGlobalBlock : block的内存在全局区,使用retain,copy, release都对其无效,block依旧存在全局区,且没有释放, 使用copy和retian只是返回block的指针,引用计数器仍然是1;
NSStackBlock : block的内存在栈区,使用retain,release操作都无效,栈区block会在方法返回后将block空间回收,类似局部变量,方法结束之后就会被回收。
NSMallocBlock : block的内存在堆区,支持retian,release,但是输出block的retainCount的计数始终是1,尽管如此内存中还是会对引用计数进行管理,使用retain引用+1, release引用-1; 对于NSMallocBlock使用copy之后不会产生新的block,只是增加了一次引用,类似于使用retian。
2.Block引用外部变量
block引用外部变量,这个要说起来比较复杂,我们先来分析一下,block到底是什么,首先写一个block,然后我们通过clang (clang -rewrite-objc file.m)进行编译,生成.cpp文件,看看里面预编译之后是什么东西
从上面的预编译文件可以看出,其实block本质上就是一个结构体,预编译的时候,首先是生成一个__main_block_impl_0这样一个结构体,这个结构体的名字其实是有规则的 main表示的是当前所在的函数,block_imp固定变量,0当前是第几个block,公式:__(当前函数名)_block_impl_(所在函数的block的位置),比如
结构体 1. __block_impl
这个是编译器自动生成的结构体,结构体的定义如下
struct __block_impl {
void *isa; //指针
int Flags; // 系统变量
int Reserved; //系统变量
void *FuncPtr; //函数指针,指向结构体实现函数的指针。这里指向的是__main_block_func_0
};
结构体 2. ___main_block_func_0
这个结构体是block内部的实现方法,编译器根据block的代码实现生成的全局态函数,会被赋值给 __block_impl .FuncPtr。
结构体3. __main_block_desc_0
这个结构体是对block代码生成的结构体的描述,主要作用是获取一下__main_block_impl_0结构体的size,也就是大小。__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; 这行代码的作用就是获取结构体__main_block_impl_0的大小,并且把这个变量用 __main_block_desc_0_DATA 保存下来。
结构体4 __main_block_impl_0:
编译器给我们在main函数中定义的block,void (^block)() = ^()};
生成的对应的结构体
struct __main_block_impl_0 {
struct __block_impl impl; //__block_impl 变量impl
struct __main_block_desc_0* Desc; //__main_block_desc_0 指针,指向编译器给我们生成的结构体变量__main_block_desc_0_DATA __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { //结构体的构造函数
impl.isa = &_NSConcreteStackBlock; //说明block是栈block impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;}};
网友评论