block是什么
通俗的理解:block就是将一些代码封装起来,以便在将来某个时候被使用,如果你不去调用block,block内部封装的代码就不会执行。举一个简单的例子,下面在main函数中定义一个最简单的block
@autoreleasepool {
^{
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"I am a block!");
};
return 0;
}
********************** 运行结果 ************************
Program ended with exit code: 0
运行程序运行可以看到block内的代码是没有运行的,因为没有调用。Block的使用也很简单,可以像函数一样被使用。加上()
就代表调用,如下
@autoreleasepool {
^{
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"I am a block!");
}();
return 0;
}
********************** 运行结果 ************************
2019-05-28 17:18:56.992746+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992924+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992939+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992947+0800 Interview03-block[2640:180864] I am a block!
Program ended with exit code: 0
如果上面写的太简练不习惯的话,通常大家可能是这么写
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"I am a block!");
};
block();
********************** 运行结果 ************************
2019-05-28 17:18:56.992746+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992924+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992939+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992947+0800 Interview03-block[2640:180864] I am a block!
Program ended with exit code: 0
至于block的书写语法请自行搞定。
你可能在面试中被这么问过:Block的本质是什么?
显然,仅仅通过上面的知识,肯定不会让面试官满意的。这里介绍一张block的底层结构图
关于此图有如下二点解释:
- block的本质也是一个OC对象,它内部也有个isa指针
- block是封装了
函数调用
和函数调用环境
的OC对象
第一点很容易看出,block地层结构图中的第一个成员就是一个isa指针,所以我们可以将block当成一个对象来看待。那么第二点中的 函数调用
和 函数调用环境
是什么意思呢?我们一步一步来。
我们先将上面的代码扩展一下
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void (^block)(int, int) = ^(int c, int b){
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"c = %d",c);
NSLog(@"b = %d",b);
NSLog(@"a的值为%d",a);
};
block(50,100);
}
return 0;
}
********************** 运行结果 ************************
2019-05-29 17:13:56.021422+0800 Interview03-block[9455:836127] I am a block!
2019-05-29 17:13:56.021630+0800 Interview03-block[9455:836127] I am a block!
2019-05-29 17:13:56.021639+0800 Interview03-block[9455:836127] c = 50
2019-05-29 17:13:56.021649+0800 Interview03-block[9455:836127] b = 100
2019-05-29 17:13:56.021700+0800 Interview03-block[9455:836127] a的值为10
Program ended with exit code: 0
上面的代码可以看出,block里面使用了它上面的 int a = 10
,可以将这个先简单的理解成函数调用环境,顾名思义,就是block所用到的一些外部变量。
函数调用指的就是上面包含那5句打印代码的匿名函数,block被调用的时候,该函数就会被调用。
接下来,我们在命令行中,使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
将main.m文件转换成C++中间代码,来仔细研究一下block的底层构造。在main.cpp文件中,将代码直接拉到底部,可以发现我们需要的内容。核心代码整理后如下,下面的代码展示了运行状态下block内的实际内容
#import <Foundation/Foundation.h>
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void (^block)(int, int) = ^(int c, int b){
NSLog(@"I am a block!");
NSLog(@"I am a block!");
NSLog(@"c = %d",c);
NSLog(@"b = %d",b);
NSLog(@"a的值为%d",a);
};
struct __main_block_impl_0 *tmpBlock = (__bridge struct __main_block_impl_0 *)block;
block(50,100);
}
return 0;
}
我们将block的底层结构struct __main_block_impl_0
直接般到main.m
里面,同时,还需要将struct __block_impl
以及struct __main_block_desc_0
也搬过来,以保证编译正确,这样,我们便可以通过struct __main_block_impl_0
来读取运行时下,block中的内容
struct __main_block_impl_0 *tmpBlock = (__bridge struct __main_block_impl_0 *)block;
我们可以在调试窗口看到如下信息
接下来我们在block内部的代码段加上断点
然后通过
Debug
-->DebugWorkflow
-->Always Show Disassembly
查看一下此时的汇编代码
Interview03-block`__main_block_invoke:
0x100000e90 <+0>: pushq %rbp
0x100000e91 <+1>: movq %rsp, %rbp
0x100000e94 <+4>: subq $0x20, %rsp
0x100000e98 <+8>: leaq 0x1c1(%rip), %rax ; @"I am a block!"
0x100000e9f <+15>: movq %rdi, -0x8(%rbp)
0x100000ea3 <+19>: movq %rdi, %rcx
0x100000ea6 <+22>: movl %esi, -0xc(%rbp)
0x100000ea9 <+25>: movl %edx, -0x10(%rbp)
0x100000eac <+28>: movq %rcx, -0x18(%rbp)
-> 0x100000eb0 <+32>: movq %rdi, -0x20(%rbp)
0x100000eb4 <+36>: movq %rax, %rdi
0x100000eb7 <+39>: movb $0x0, %al
0x100000eb9 <+41>: callq 0x100000f16 ; symbol stub for: NSLog
0x100000ebe <+46>: leaq 0x19b(%rip), %rcx ; @"I am a block!"
0x100000ec5 <+53>: movq %rcx, %rdi
0x100000ec8 <+56>: movb $0x0, %al
0x100000eca <+58>: callq 0x100000f16 ; symbol stub for: NSLog
0x100000ecf <+63>: leaq 0x1aa(%rip), %rcx ; @"c = %d"
0x100000ed6 <+70>: movl -0xc(%rbp), %esi
0x100000ed9 <+73>: movq %rcx, %rdi
0x100000edc <+76>: movb $0x0, %al
0x100000ede <+78>: callq 0x100000f16 ; symbol stub for: NSLog
0x100000ee3 <+83>: leaq 0x1b6(%rip), %rcx ; @"b = %d"
0x100000eea <+90>: movl -0x10(%rbp), %esi
0x100000eed <+93>: movq %rcx, %rdi
0x100000ef0 <+96>: movb $0x0, %al
0x100000ef2 <+98>: callq 0x100000f16 ; symbol stub for: NSLog
0x100000ef7 <+103>: leaq 0x1c2(%rip), %rcx ; @
0x100000efe <+110>: movq -0x20(%rbp), %rdi
0x100000f02 <+114>: movl 0x20(%rdi), %esi
0x100000f05 <+117>: movq %rcx, %rdi
0x100000f08 <+120>: movb $0x0, %al
0x100000f0a <+122>: callq 0x100000f16 ; symbol stub for: NSLog
0x100000f0f <+127>: addq $0x20, %rsp
0x100000f13 <+131>: popq %rbp
0x100000f14 <+132>: retq
这里不用纠结汇编具体写了什么,从最右边栏的信息,我们也能大致猜测出这段汇编码应该就是block中所包裹的那段函数体实现,事实也确实如此,这里我们只需要注意第一句汇编码最左侧的0x100000e90
,这个就是block中所包含的函数实现的入口地址。而刚才我们的截图中block内部所包含的FuncPtr = (void *)0x100000e90
,也确实验证这个FuncPtr
所指向的就是block内部所包含的那段函数实现的入口。
小结一
- block的本质是一个OC对象
因为block底层结构的第一个成员实际上是一个isa指针
。- block封装了函数调用和函数调用环境。
因为block的内部存储了block函数
实现的入口地址(函数调用),还捕获了block函数
所用到的外部的一些变量(函数调用环境)。