1. 将Block转换为普通C语言
通过支持Block的编译器,可以将含有Block语法的源代码转换为一般C语言编译器能给处理的源代码,并作为极为普通的C语言源代码被编译
以下命令在终端中将Objective-C转换为C语言源代码:
clang -rewrite-obj 源码文件
2. Block分析
以下列源码为例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int val = 50;
const char *text = "val = %d\n";
void (^blk)(void) = ^{
printf(text, val);
};
val = 100;
blk();
}
return 0;
}
转换后:
//Block的实现类
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//Block的封装类
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *text; //block截获的自动变量
int val; //block截获的自动变量
//构造函数,将语法表达时所使用的自动变量保存到结构体内,实现自动变量值的截获
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_text, int _val, int flags=0) : text(_text), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//Block体的实现,__cself相当于C++的this,或Objective-C的self
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *text = __cself->text;
int val = __cself->val;
printf(text, val);
}
//__main_block_desc_0类,__main_block_desc_0_DATA为其对象
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int val = 50;
const char *text = "val = %d\n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, text, val));
val = 100;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
所以Block的实质为Objective-C的对象,对不同形式Block进行转换后可以的到如下的Block大致结构:
上图的变量部分,即是Block截取的自动变量,"截获自动变量"意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block结构体实例(即Block自身)中,可以理解为Block中存放了一个自动变量的副本
- Block中的Var部分可以有多个变量,这取决于Block中截取的自动变量的个数
- 副本类型基本与自动变量的类型一致,若自动变量为静态变量,则副本类型为指针,若自动变量为有
__block
修饰符,则副本类型为__Block_byref_val
类型- Block不会截取全局变量和静态全局变量
源码:
int global_val = 1;
static int static_global_val = 2;
int main() {
static int static_val = 3;
__block int val = 16;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
val *= 16;
}
}
转换后的Block类结构:
带有`__block`修饰符的变量实际上是存放在`__Block_byref_val`中的
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *forwarding;
int __flags;
int __size;
int val;
}
struct __main_block_impl_0 {
//Impl
struct __block_impl impl;
//Desc
struct __main_block_desc_0 *desc;
//Var(变量部分)
int *static_val;
__Block_byref_val_0 *val;
//这里__Block_byref_val_0对象的调用都是通过内部的forwarding指针操作
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags):val(_val->forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val;
__Block_byref_val_0 *val = __cself->val;
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
//__Block_byref_val类型
(val->forwarding->val) *= 16;
}
Block内部实现套路总结
//Block的实现类
struct Impl {
void *isa;
int Flags;
int Reserved;
void *funcPtr;
};
//Block的封装类
struct Block {
struct Impl impl;
struct Desc * Desc;
Var val; //截获的自动变量
//构造函数,将语法表达时所使用的自动变量保存到结构体内,实现自动变量值的截获
Block(void *funcPtr, struct Desc *desc, Var _val) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//funcPtr指向的函数
static void func(struct Block *__cself) {
//从Block中读取val
Var val = __cself->val;
//block方法体中的操作
printf(text, val);
}
//Desc类
static Desc {
size_t reserved;
size_t Block_size;
} descData = { 0, sizeof(struct Block)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Var val = 50;
const char *text = "val = %d\n";
blk = &Block(&func, descData, val);
blk->funcPtr(blk);
}
return 0;
}
像静态变量截获为指针的方式能够达到与__block修饰符一样的效果,为什么不使用这种的方式?
变量作用域结束的同时,原来的自动变量被废弃,因此Block中超过变量作用域而存在的变量将不能通过指针访问原来的自动变量
Block的isa指针与存储域
在之前的转换block的代码里,会出现impl.isa = &_NSConcreteStackBlock;
,isa指向的是_NSConcreteStackBlock
类,还有一些与之类似的类:
- _NSConcreteStackBlock:表示该类的Block对象设置在栈上
- _NSConcreteGlobalBlock:表示该类的Block对象与全局变量一样设置在数据区域(.data区)
- _NSConcreteMallocBlock:表示该类的Block对象由malloc分配在堆上
Block分配在数据区域
在记述全局变量的地方使用Block,生成的Block为_NSConcreteGlobalBlock
类对象
void (^blk)(void) = ^{print("Global Block\n");}
int main() {
blk();
}
该Block的类为_NSConcreteGlobalBlock
,该Block实例则设置在数据区域中,因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获。
typedef int(^blk_t)(int);
for(int rate = 0; reate < 10; ++rate) {
blk_t blk = ^(int count) {return count;}
}
在for循环里Block并没有去截获自动变量,所以blk实际是设置在程序的数据区域,虽然通过clang转换的代码通常是_NSConcreteStackBlock
。所以总结如下:
- 记述全局变量的地方有Block语法
- Block语法表达式中不使用应截获的自动变量时
Block的isa指向的是_NSConcreteGlobalBlock
Block分配在堆
设置在栈上的Block,如果其所说变量作用域结束,该Block就被废弃,由于__block
变量也在栈上,如果其所属变量作用域结束,__block
变量也会被废弃掉
Block提供了将Block和__block
从栈复制到堆上的方法,当ARC有效时大多数情况下编译器会进行恰当地判断,自动将Block从栈复制到堆上
Block从栈复制到堆上相当消耗CPU
源码:
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count){return rate * count;};
}
转换后:
blk_t func(int rate) {
//通过Block语法生成Block类型变量tmp
blk_t tmp = &__func_block_impl_0 (
__func_block_func_0, &__func_block_desc_0_DATA, rate);
//通过调用Block_copy将栈上的Block变量复制到堆上,将指针赋值给变量tmp
tmp = objc_retainBlock(tmp);
//将堆上的Block作为Objective-C对象注册到autoreleasepool中,然后返回该对象
return objc_autoreleaseReturnValue(tmp);
}
Block需要从栈复制到堆上的情形:
- 向方法或者函数的参数中传递Block(需手动调用copy方法)
- Block作为方法或者函数的返回值(编译器自动复制)
- 将Block赋值给附有
__strong
修饰符的id类型的类或者Block类型的变量(编译器自动复制) - Cocoa框架的方法且方法中含有usingBlock等时(编译器自动复制)
- Grand Centeal Dispatch的API(编译器自动复制)
除了第一种情形需要手动调用copy方法外,其他几种编译器会自动进行判断
- (id)getBlockArray {
int val = 10;
return [[NSArray alloc]initWithObject:
^{NSLog(@"block0:%d, val")},
^{NSLog(@"block0:%d, val")}, nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
blk()
在执行的时候会发生异常,应用程序强制结束,正确方式如下:
- (id)getBlockArray {
int val = 10;
return [[NSArray alloc]initWithObject:
[^{NSLog(@"block0:%d, val")} copy],
[^{NSLog(@"block0:%d, val")} copy], nil];
}
copy操作对于不同Block类的效果如下:
Block的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteStackBlock | 堆 | 引用计数增加 |
__block变量存储域
- 若1个Block中使用
__block
变量,则当该Block从栈复制到堆时,使用的__block变量也全部从栈复制到堆 - 多个Block中使用
__block
变量时,因为最先会将所有Block配置在栈上,所以__block
变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block
变量也会一并从栈复制到堆,并被该Block所持有。而当剩下的Block从栈中复制到堆时,被复制的Block持有堆中的__block
变量,并增加__block
变量的引用计数
如果配置在堆上的Block被废弃,那么它所使用的
__block
变量也就被释放了栈上的__block
变量在从栈复制到堆上时会将成员变量_forwarding的值替换为复制到堆上的__block
变量的地址
通过该功能,无论是在Block语法内还是在Block语法外,或者是在栈上或者堆上,都能正确访问同一个
__block
变量
在将__block
变量从栈复制到堆上或者释放时,会调用以下方法:
//复制
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) {
_Block_object_assign(&dst->val, &src->val, BLOCK_FIELD_IS_BYREF);
}
//释放
static void __main_block_dispose_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) {
_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}
BLOCK_FIELE_IS_BYREF:表示该变量是__block
变量
BLOCK_FIELE_IS_OBJECT:表示该变量是对象
__block与__strong、__weak
_block id obj = [[NSObject alloc] init];
等价于
__block id __strong obj = [[NSObject alloc] init];
ARC有效时,id类型以及对象类型变量必定附加所有权修饰符,缺省为附有__strong修饰符的变量
blk_t blk1;
blk_t blk2;
{
//array1被复制到堆上
id array1 = [[NSMutableArray alloc] init];
blk1 = [^(id obj) {
[array1 addObject:obj];
NSLog(@"array1 count = %ld", [array1 count]);
} copy];
id __weak array2 = [[NSMutableArray alloc] init];
blk2 = [^(id obj) {
[array2 addObject:obj];
NSLog(@"array2 count = %ld", [array2 count]);
} copy];
}
blk1([[NSObject alloc] init]);
blk1([[NSObject alloc] init]);
blk1([[NSObject alloc] init]);
blk2([[NSObject alloc] init]);
blk2([[NSObject alloc] init]);
blk2([[NSObject alloc] init]);
执行结果如下:
array1 count = 1
array1 count = 2
array1 count = 3
array2 count = 0
array2 count = 0
array2 count = 0
参考
- Objective-C高级编程(iOS与OS X多线程和内存管理)