查看LLVM官方文档Block-ABI-Apple
一、不含变量Block
void (^blk)(void) = ^{
printf("hello world");
};
int main(int argc, const char * argv[]) {
blk();
return 0;
}
通过Clang命令,可以看到编译的C++文件:
clang -rewrite-objc main.m
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
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[]) {
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
__main_block_func_0
这个是block的执行函数,参数是__main_block_impl_0
类型。
__main_block_impl_0
这个结构体就是Block的底层实现了
- isa:指向的是_NSConcreteGlobalBlock,这个是全局block
- Flags:标志
- FuncPtr:实现函数指针
- Desc:block的一些信息
上面的情况是将block的定义在函数外,在函数内引用,特别的,当把block的定义放入函数体内,如下
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{
printf("hello world dk");
};
NSLog(@"%@", blk);
return 0;
}
clang命令后:
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;
}
};
isa指针由_NSConcreteGlobalBlock
变为_NSConcreteStackBlock
但根据LLVM实际打印结果
lockDemo[59807:10443592] <__NSGlobalBlock__: 0x100001040>
所以应当还是GlobalBlock,这里clang与LLVM有点不同。以LLVM为准。
二、含变量的Block
加了一个变量int i;
int main(int argc, const char * argv[]) {
int i = 100;
void (^blk)(void) = ^{
printf("hello world %d",i);
};
NSLog(@"%@",blk);
blk();
return 0;
}
打印结果
// MRC
BlockDemo[59851:10455160] <__NSStackBlock__: 0x7fff5fbff6a8>
// ARC
BlockDemo[60278:10523410] <__NSMallocBlock__: 0x10055e4b0>
Clang之后变成
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
printf("hello world %d",i);
}
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[]) {
int i = 100;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通过对比可发现,isa指针已经指向NSConcreteStackBlock,Block被复制到了堆上,而且函数多个几个i
_main_block_impl_0
结构体中多了一个i,成功将i变量的值捕获。因为main函数中的局部变量i和函数__main_block_func_0不在同一个作用域中,调用过程中只是进行了值传递。main函数的i是在栈中,block是在堆上,所以不能对值进行修改。
如果想要在block内修改i的值,会报错
三、Block内修改外部变量
我们都知道,在block内部修改变量的话,要在变量前面加上修饰符’__block’;
int main(int argc, const char * argv[]) {
int i = 100;
__block int block_i = i;
void (^blk)(void) = ^{
block_i = 50;
};
NSLog(@"%@",blk);
blk();
return 0;
}
打印结果
// MRC
BlockDemo[59851:10455160] <__NSStackBlock__: 0x7fff5fbff6a8>
// ARC
BlockDemo[60278:10523410] <__NSMallocBlock__: 0x10055e4b0>
Clang之后变成
struct __Block_byref_block_i_0 {
void *__isa;
__Block_byref_block_i_0 *__forwarding;
int __flags;
int __size;
int block_i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_block_i_0 *block_i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_block_i_0 *_block_i, int flags=0) : block_i(_block_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_block_i_0 *block_i = __cself->block_i; // bound by ref
(block_i->__forwarding->block_i) = 50;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_i, (void*)src->block_i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
int i = 100;
__attribute__((__blocks__(byref))) __Block_byref_block_i_0 block_i = {(void*)0,(__Block_byref_block_i_0 *)&block_i, 0, sizeof(__Block_byref_block_i_0), i};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_block_i_0 *)&block_i, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
可与发现,大概多了三个部分
当block从栈上被copy到堆上时,会调用__main_block_copy_0
将__block类型的成员变量i从栈上复制到堆上,同时,栈上的__Block_byref_i_0
结构体中的__forwarding指针也会指向堆上的地址。而当block被释放时,相应地会调用_main_block_dispose_0
来释__block类型的成员变量i。
这样,就成功地修改变量的值。
修改外部变量
引用外部变量,值传递,只能值进行读操作,不能写
修改外部变量,在变量前加__block可以进行写操作
ARC与MRC
因此在ARC情况下,创建的block仍然是NSConcreteStackBlock类型,只不过当block被引用或返回时,ARC帮助我们完成了copy和内存管理的工作变成NSConcreteMallocBlock放在堆上。
在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。