一、Block的本质
在底层Block是以C语言结构体实现的,而在OC中Block的本质即为OC对象。
通过 "
clang -rewrite-objc” 对OC代码进行重新
^{
printf
(
"tempBlock"
);};
这个Block会被转换成如下结构:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static
struct
__main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0 , sizeof ( struct __main_block_impl_0)};
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0 , sizeof ( struct __main_block_impl_0)};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
每个OC对象都有一个isa指针指向其类对象,类似的通过转换后代码可以看到“
impl.isa = &_NSConcreteStackBlock;”Block的isa指针指向了“
_NSConcreteStackBlock”类对象。即该Block为
_NSConcreteStackBlock类对象。
除了_NSConcreteStackBlock之外还有两个与之类似的类_NSConcreteGlobalBlock和_NSConcreteMallocBlock。不同的Block类对象指示了不同的存储域对应不同的OC类型,具体整理如下:
clang类 | OC类 | 存储域 |
_NSConcreteGlobalBlock | __NSGlobalBlock__ | 静态数据区 |
_NSConcreteStackBlock | __NSStackBlock__ | 栈 |
_NSConcreteMallocBlock | __NSMallocBlock__ | 堆 |
通过clang -rewrite-objc Block只定义了_NSConcreteStackBlock和_NSConcreteGlobalBlock两种类型。而在OC中确实是存在三种类型的Block为了便于理解我们加入了“_NSConcreteMallocBlock”,OC中的Block类型可以通过简单的“NSLog”来查看。
二、
_NSConcreteGlobalBlock
通常情况下Block都是_NSConcreteStackBlock类对象,都设置在栈上。但实际上并非全是这样:
在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获。由此Block的结构体实例的内容不依赖与执行时的状态,所以整个程序只需一个实例。因此将Block用结构体实例设置在与全局变量相同的数据区域即可。
即使在函数内使用Block语法,在Block未截获任何变量的情况下,Block的结构体实例的内容同样不依赖与执行时状态,所以整个程序同样只需一个实例,此时Block结构体实例同样设置在静态数据区。在这种情况下通过 clang -rewrite-objc 转换的源代码通常是_NSConcreteStackBlock类对象,但实际上却不同,通过NSlog得到的对象为“__NSGlobalBlock__”。总结如下:
- 在记述全局变量的地方使用Block语法时生成的Block为_NSConcreteGlobalBlock类对象。
- Block语法的表达式中不使用应截获的变量时生成的Block为_NSConcreteGlobalBlock类对象。
三、
_NSConcreteMallocBlock
配置在全局变量上的Block,从变量作用域外也可以通过指针安全的使用。但设置在栈上的Block,如果其所属的变量作用域结束。该Block就被废弃,从变量作用域外访问该Block会导致程序崩溃。Blocks提供了将Block从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。
当一个栈上的Block被复制到堆上时它的内存管理就和一个OC对象一样了(引用计数管理)。
在非ARC环境下对Block简单的执行copy操作即可将Block从栈上复制到堆上,需要注意的是对copy后的Block执行release操作以防止内存泄漏。
在非ARC环境下当方法返回一个Block时通常的做法是 :
return
[[stackBlock copy] autorelease];
copy操作将Block复制到堆上,
autorelease操作防止内存泄漏。
在ARC环境下,大多数情况下编译器会进行恰当的判断,自动生成将Block从栈上复制到堆上的代码,总结如下:
- 当Block被strong类型指针引用时
- 当Block作为方法返回值返回时
- 当Block属性被copy修饰符修饰时
对于不同类型Block的copy操作总结如下:
Block的类 | 存储域 | 复制效果 |
_NSConcreteGlobalBlock | 静态数据区 | 什么也不做 |
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteMallocBlock | 堆 | 增加引用计数 |