前面有一篇介绍 Block 的博客 ,主要介绍了 Block 的简单使用技巧。这篇博客主要更加深入地了解一下 Block。包括:Block 的实现、__Block 的原理以及 Block 的存储域三方面。
首先我们使用 Xcode 创建一个 Project, 点击 File-->New-->Project,选择 macOS 中 Application 的 Command Line Tool,然后设置 Project Name 即可。你好发现这个工程值包含了一个 main.m 文件,然后我们做如下更改(更改后的代码如下):
- #import < stdio.h >
- int main(int argc, const char * argv[]) {
- printf("Hello World");
- return 0;
- }
这个是我们最常见的 C 代码,导入 stdio.h,然后打印出来 Hello World。接下来我们写一个最简单的 block,没有返回值,没有传入参数:
- #import < stdio.h >
- int main(int argc, const char * argv[]) {
- void( ^ blk)(void) = ^{
- printf("Hello Worldddd");
- };
- blk();
- return 0;
- }
打印出来的结果相当于调用了 blk 输出的结果。接下来我们在 item 中跳转到 main.m 所在文件夹然后执行如下命令:
- clang - rewrite - objc main.m
你会发现在当前文件夹下生成了一个. cpp 文件,它是经过 clang 编译器编译之后的文件,打开之后里面大概有 5 百多行,其实我们看下面的这些代码就足够了:
- 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;
- }
- };#ifndef BLOCK_IMPL#define BLOCK_IMPL struct __block_impl {
- void * isa;
- int Flags;
- int Reserved;
- void * FuncPtr;
- };
- static void __main_block_func_0(struct __main_block_impl_0 * __cself) {
- printf("Hello Worldddd");
- }
- 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_impl_0、__block_impl、__main_block_desc_0
和两个方法:
- __main_block_func_0、main
main 就是我们写的 main 函数。
至此,你能知道的就是:Block 看上去很特别,其实就是作为及其普通的 C 语言源代码来处理的。编译器会把 Block 的源代码转换成一般的 C 语言编译器能处理的源代码,并作为极为普通的 C 语言源代码被编译。
接下来对编译的内容来一个分解,首先是
- ^{printf("Hello Worldddd")};
变换后的源代码如下:
- static void __main_block_func_0(struct __main_block_impl_0 * __cself) {
- printf("Hello Worldddd");
- }
也就是现在变成了一个静态方法,其命名方式为:Block 所属的函数名 (main) 和该 Block 语法在函数出现的顺序值 (0) 来给经过 clang 变换的函数命名。该方法的参数相当于我们在 OC 里面的指向自身的 self。我们看一下该参数的声明:
- struct __main_block_impl_0 * __cself
你会发现它其实是一个结构体,该结构体的声明如下:
- 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;
- }
- };
该结构体中你会发现里面有一个构造函数,你忽略构造函数,会发现该结构体就很简单了,只是包含了 impl 和 Desc 两个属性变量。其中 impl 也是一个结构体,它的结构如下:
- struct __block_impl {
- void * isa;
- int Flags;
- int Reserved;
- void * FuncPtr;
- };
从属性变量的名字我们可以猜测出该结构体各个属性的含义:
第二个变量是 Desc,也是一个结构体:
- 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)};
这个结构体就比较简单了,一个预留位,一个是指代该 Block 大小的属性,后面又包含了一个该实例:
- __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
预留位为 0,大小为传入结构体的大小。接下来就是很重要的构造函数了:
- __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;
- }
其中的 &_NSConcreteStackBlock 用于初始化 impl 的 isa 指针;flags 为 0;FuncPtr 是构造函数传过来的 fp 函数指针;Desc 为一个 block 的描述。到这里三个结构体和一个函数就介绍完了。接下来看一下 main 函数里面上述构造函数是如何调用的:
- void
- (*blk)(
- void
- ) = ((
- void
- (*)())&__main_block_impl_0((
- void
- *)__main_block_func_0, &__main_block_desc_0_DATA));
感觉好复杂,我们先做一个转换:
- struct __main_block_impl_0 tmpeImpl = __main_block_impl_0((void * ) __main_block_func_0, &__main_block_desc_0_DATA)
- struct __main_block_impl_0 * blk = &tmpeImpl;
也就是说把结构体的实例的指针赋值给 blk。接下来再看一下构造函数的的初始化,其实赋值就变成了这样:
- impl.isa = &_NSConcreteStackBLock;
- impl.Flags = 0;
- impl.FuncPtr = __main_block_func_0;
- Desc = &__main_block_desc_0_DATA;
现在在看一下调用 block 的那句代码:
转换成了:
- ((void( * )(__block_impl * ))((__block_impl * ) blk) - >FuncPtr)((__block_impl * ) blk);
这个转换不是太明白,但是知道他的作用就是把 blk 当做参数传进去,调用的 FuncPtr 所指向的函数,也就是__ block _ block _ func _ 0。
到这里就大体了解了 Block 的实现,其实就是 C 的几个结构体和方法,经过赋值和调用,进而实现了 Block。
另外 Block 其实实质上也是 OC 的对象。
- #import < stdio.h >
- int main(int argc, const char * argv[]) {
- int i = 3;
- void( ^ blk)(void) = ^{
- printf("Hello World,%d", i);
- };
- blk();
- return 0;
- }
使用 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);
- }
- int main(int argc, const char * argv[]) {
- int i = 3;
- 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;
- }
也就是在 main 函数调用的时候把 i 传到了构造函数里,然后通过 i(_i) 对结构体的属性变量 i 赋值,i 变量现在已经成为了结构体的一个树形变量。在构造函数执行时把 i 赋值。在 main _ block_func _ 0 里面通过 cself 调用,这个变量实际是在声明 block 时,被复制到了结构体变量 i,因此不会影响变量 i 的值。当我们尝试在 Block 中去修改时,你会得到如下错误:
- Variable is not assignable(missing __block type specifier)
提示我们加上__block, 接下来我们将源代码做如下修改:
- int main(int argc, const char * argv[]) {
- __block int i = 3;
- void( ^ blk)(void) = ^{
- i = i + 3;
- printf("Hello World,%d", i);
- };
- blk();
- return 0;
- }
运行一下你会发现你成功对 i 的值进行了修改!用 clang 进行编译,结果如下:
- struct __Block_byref_i_0 {
- void *__isa;
- __Block_byref_i_0 *__forwarding;
- int __flags;
- int __size;
- int i;
- };
- struct __main_block_impl_0 {
- struct __block_impl impl;
- struct __main_block_desc_0* Desc;
- __Block_byref_i_0 *i; // by ref
- __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_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_i_0 *i = __cself->i; // bound by ref
- (i->__forwarding->i) = (i->__forwarding->i) + 3;
- printf("Hello World,%d",(i->__forwarding->i));
- }
- static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
- static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->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[]) {
- __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 3};
- void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
- ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
- return 0;
- }
你会发现多了一个__ Block _ byref i 0 的结构体,然后多了两个 copy 和 dispose 函数。
看一下 main 函数里面的 i,此时也不再是一个简单的基本类型 int,而是一个初始化的 __ Block _ byref _ i _ 0 的结构体,该结构体有个属性变量为 i,然后把 3 赋值给了那个属性变量。该结构体还有一个指向自己的指针 __ forwarding,它被赋值为 i 的地址。
现在 __ main _ block _ func _ 0 在实现中使用了指向该变量的指针,所以达到了修改外部变量的作用。
Block 的存储域有以下几种:
我们前面看到的都是在 Stack 的 Block,但是你可以在 OC 工程中打印一下你声明的 block 的 isa,你会发现它其实是 Malloc 的 block,也就是在堆上的 block。如图:
还有一种情况是 Global 的 block:
在 ARC 中,只有 NSConcreteGlobalBlock 和 NSConcreteMallockBlock 两种类型的 block。因为我们最简单的 block 在工程中打印出来的都是 MallocBlock。也许是因为苹果把对象都放到了堆管理,而 Block 也是对象,所以也放到了堆上。
此时我们也许会有个疑问:Block 超出了变量作用域为什么还能存在呢?
对于 Global 的 Block,变量作用域之外也可以通过指针安全使用,但是设置在栈上的就比较尴尬了,作用域结束后,Block 也会 被废弃。为了使 Block 超出变量作用域还可以存在,Block 提供了将 Block 和 __ block 变量从栈上复制到堆上的方法来解决这个问题。这样就算栈上的废弃,堆上的 Block 还可以继续存在。
看一下对 Block 进行复制,结果如何:
如果对 Block 进行了 copy 操作,__ block 的变量也会受到影响,当 __ block 的变量配置在栈上,复制之后它将从栈复制到堆上并被 Blcok 持有,如果是堆上的 __ block 变量,Blcok 复制之后该变量被 Block 持有。
如果两个 block(block1,block2)同时都是用 __ block 变量,如果 block1 被复制到了堆上,那么 __ block 变量也会在 block1 复制到堆的同时复制到堆上,当 block2 再是用到 __ block 变量的时候,只是增加堆上 __ block 变量的引用计数,不会再次复制。如果堆上的 block1 和 block2 被废弃了,那么它所是用的 __ block 变量也就被释放了(如果 block1 被废弃,而 block2 没有被废弃,那么 __ block 变量的引用计数 - 1,直到最后使用 __ block 变量的 block 被废弃的同时,堆上的 __ block 也会被释放)。
理解了上面刚才说的复制之后,现在回过来思考另一个问题: __ block 的时候转换的结构体中的 __ forwarding 指针有什么作用呢?(下面代码中的 __ forwarding)
- struct __Block_byref_i_0 {
- void * __isa;
- __Block_byref_i_0 * __forwarding;
- int __flags;
- int __size;
- int i;
- };
其实是这样的:栈上的 __ block 变量用结构体实例在 __ block 变量从栈复制到堆上的时候,会将成员变量 __ forwarding 的值替换为复制目标堆上的 __ block 变量用结构体实例的地址。通过该操作之后,无论是在 Block 语法中、Block 语法外使用 __ block 变量,还是 __ block 变量配置在栈上或者堆上,都可以顺利地访问同一个 __ block 变量。
以上便是对 block 的进一步介绍,主要参考了《Objective-C 高级编程 iOS 与 OS X 多线程和内存管理》一书。
来源: https://www.cnblogs.com/zhanggui/p/8135790.html