Block
简介block
是将函数及其执行上下文封装起来的一个对象block
实现的内部,有不少变量,由于block
也是一个对象isa
指针,imp
指针等对象变量,还有储存其截获变量的对象等block
根据有无参数和有无返回值有如下几种简单使用方式html
// 无参数无返回值 void (^ BlockOne)(void) = ^(void){ NSLog(@"无参数,无返回值"); }; BlockOne();//block的调用 // 有参数无返回值 void (^BlockTwo)(int a) = ^(int a){ NSLog(@"有参数,无返回值, 参数 = %d,",a); }; BlockTwo(100); // 有参数有返回值 int (^BlockThree)(int,int) = ^(int a,int b){ NSLog(@"有参数,有返回值"); return a + b; }; BlockThree(1, 5); // 无参数有返回值 int(^BlockFour)(void) = ^{ NSLog(@"无参数,有返回值"); return 100; }; BlockFour(); 复制代码
但是以上四种block
底层又是如何实现的呢? 其本质到底如何? 接下来咱们一块儿探讨一下c++
Command Line Tool
项目, 在main
函数中执行上述中一个block
Block
的本质, 就要查看其源码, 这里咱们使用下面命令把main.m
文件生成与其对应的c++
代码文件main.m
文件所在的目录下, 执行以下命令, 会生成一个main.cpp
文件main.cpp
文件添加到项目中, 并使其不参与项目的编译, 下面咱们就具体看一下block
的底层究竟是如何实现的xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
复制代码
打开main.cpp
文件, 找到文件最底部, 能够看到block
的相关源码以下程序员
// block的结构体 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; } }; // 封装了block执行逻辑的函数 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_11c959_mi_0); } 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; // 定义block变量 void (* BlockOne)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); // 执行block内部的源码 ((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; 复制代码
其中block
的声明和调用的对应关系以下bash
删除其中的强制转换的相关代码后微信
// 定义block变量 void (* BlockOne)(void) = &__main_block_impl_0( (void *)__main_block_func_0, &__main_block_desc_0_DATA ); // 执行block内部的源码 BlockOne->FuncPtr(BlockOne); 复制代码
上述代码中__main_block_impl_0
函数接受两个参数, 并有一个返回值, 最后把函数的地址返回给BlockOne
, 下面找到__main_block_impl_0
的定义markdown
// 结构体 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // c++中的构造函数, 相似于OC中的init方法 // flags: 默认参数, 调用时可不传 __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; } }; 复制代码
__main_block_impl_0
函数中的第一个参数__main_block_func_0
赋值给了fp
, fp
又赋值给了impl.FuncPtr
, 也就意味着impl.FuncPtr
中存储的就是咱们要执行的__main_block_func_0
函数的地址Block
结构体中的isa
指向了_NSConcreteStackBlock
, 说明Block
是一个_NSConcreteStackBlock
类型, 具体后面会详解__main_block_impl_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)}; 复制代码
reserved
赋值为0Block_size
被赋值为sizeof(struct __main_block_impl_0)
, 即为__main_block_impl_0
这个结构体占用内存的大小__main_block_impl_0
的第二个参数, 接受的即为__main_block_desc_0
结构体的变量(__main_block_desc_0_DATA
)的地址auto
和static
auto
: 自动变量, 离开做用域就会自动销毁, 默认状况下定义的局部变量都是auto
修饰的变量, 系统都会默认给添加一个auto
auto
不能修饰全局变量, 会报错static
做用域内修饰局部变量, 能够修饰全局变量
auto
局部变量在Block
中是值传递iphone
下述代码输出值为多少?函数
int age = 10; void (^BlockTwo)(void) = ^(void){ NSLog(@"age = %d,",age); }; age = 13; BlockTwo(); // 输出10 复制代码
输出值为何是10而不是13呢? 咱们仍是生成main.cpp
代码看一下吧, 相关核心代码以下oop
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // 这里多了一个age属性 int age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age); } 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 age = 10; // block的定义 void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); // 改变属性值 age = 13; // 调用block ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; 复制代码
那么下面咱们一步步看一下, 吧一些强制转换的代码去掉以后spa
int age = 10; void (*BlockTwo)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, age ); age = 13; BlockTwo->FuncPtr(BlockTwo); 复制代码
在上面的__main_block_impl_0
函数里面相比于以前的, 多了一个age
参数
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // 新的属性age int age; // 构造函数, 多了_age参数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 复制代码
__main_block_impl_0
中, 多了一个_age
参数age(_age)
语句, 在c++
中, age(_age)
至关于age = _age
, 即给age
属性赋值, 存储构造函数传过来的age
属性的值block
的时候, block
对应的结构体所存储的age
属性的值仍然是10, 并无被更新// 及时这里从新对age进行了赋值 age = 13; // 这里调用BlockTwo的时候, 结构体重的age属性的值并无被更新 BlockTwo->FuncPtr(BlockTwo); // 最后在执行block内部逻辑的时候, static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy // 这里的age, 仍然是block结构体中的age, 值并无改变, 因此输出结果仍是10 NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age); } 复制代码
static
局部变量在Block
中是指针传递, 看一下下面代码的输出状况
auto int age = 10; static int weight = 20; void (^BlockTwo)(void) = ^(void){ NSLog(@"age = %d, weight = %d,",age, weight); }; age = 13; weight = 23; BlockTwo(); 复制代码
age = 10, weight = 23
age
的结果不变, 以前已经说过了weight
的结果倒是赋值后的结果, 至于为何, 请继续向下看吧...main.cpp
代码看一下吧, 相关核心代码以下struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; int *weight; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy int *weight = __cself->weight; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight)); } 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; auto int age = 10; static int weight = 20; void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight)); age = 13; weight = 23; ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; 复制代码
__main_block_impl_0
类中多了两个成员变量age
和weight
, 说明两个变量咱们均可以捕获到int
变量, 使用不一样的修饰词修饰, __main_block_impl_0
类中也是不一样的static
修饰的变量weight
在block
中存储的是weight
的地址, 在后面的block
函数中咱们使用的也是其地址int age; int *weight; // &weight void (*BlockTwo)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age, &weight); // 下面构造方法中, 一样(weight(_weight)方法以前讲过)将传过来的weight的地址赋值给了 (int *weight;) __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } 复制代码
age
保存的是一个准确的值weight
保存的是weight
所在的内存地址block
内部逻辑的时候static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy int *weight = __cself->weight; // bound by copy // (*weight)至关于从weight的内存地址中取值, 在执行操做 // 然而weight内存中的值已经在后面赋值的时候被更新了, 因此这里取出的值是赋值后的 NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight)); } 复制代码
auto
修饰的变量在block
中存储的是变量的值(值传递)static
修饰的变量在block
中存储的是变量的内存地址(地址传递)int age = 10; static int weight = 20; int main(int argc, const char * argv[]) { @autoreleasepool { void (^BlockTwo)(void) = ^(void){ NSLog(@"age = %d, weight = %d,",age, weight); }; age = 13; weight = 23; BlockTwo(); } return 0; } 复制代码
上面代码的输出结果, 毫无疑问是13和23, 相关c++
代码以下
int age = 10; static int weight = 20; 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; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { // 封装了block执行逻辑的函数 NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_0ee0bb_mi_0,age, weight); } 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; // 定义block变量 void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); age = 13; weight = 23; ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; 复制代码
__main_block_impl_0
结构体重并无捕获到age
和weight
的成员变量block
变量的时候中也不须要传入age
和weight
的变量block
执行逻辑的函数中, 就能够直接使用全局的变量便可C++
源码中, __main_block_impl_0
结构体中isa
指向的类型是_NSConcreteStackBlock
Block
的只要类型有那些void (^block)(void) = ^(void){ NSLog(@"Hello World"); }; NSLog(@"%@", [block class]); NSLog(@"%@", [[block class] superclass]); NSLog(@"%@", [[[block class] superclass] superclass]); NSLog(@"%@", [[[[block class] superclass] superclass] superclass]); /* 2019-06-24 15:46:32.506386+0800 Block[3307:499032] __NSGlobalBlock__ 2019-06-24 15:46:32.506578+0800 Block[3307:499032] __NSGlobalBlock 2019-06-24 15:46:32.506593+0800 Block[3307:499032] NSBlock 2019-06-24 15:46:32.506605+0800 Block[3307:499032] NSObject */ 复制代码
block
的类型NSBlock
最终也是继承自NSObject
block
的结构体__main_block_impl_0
中会有一个isa
指针了block
共有三种类型, 能够经过调用class
方法或者isa
指针查看具体类型, 最终都是继承自NSBlock
类型
__NSGlobalBlock__
或者_NSConcreteGlobalBlock
__NSStackBlock__
或者_NSConcreteStackBlock
__NSMallocBlock__
或者_NSConcreteMallocBlock
_NSConcreteGlobalBlock
: 在数据区域_NSConcreteStackBlock
: 在栈区域_NSConcreteMallocBlock
: 在堆区域alloc
或者malloc
方式申请的内存block
类型, 且不一样的block
类型存放在内存的不一样位置block
究竟是哪种类型呢 看看下面代码的执行状况, 运行环境实在MRC
环境下static int age = 10; int main(int argc, const char * argv[]) { @autoreleasepool { int weight = 21; void (^block1)(void) = ^(void){ NSLog(@"Hello World"); }; void (^block2)(void) = ^(void){ NSLog(@"age = %d", age); }; void (^block3)(void) = ^(void){ NSLog(@"age = %d", weight); }; NSLog(@"block1 = %@", [block1 class]); NSLog(@"block2 = %@", [block2 class]); NSLog(@"block3 = %@", [block3 class]); /* 2019-06-24 21:13:14.555206+0800 Block[30548:1189724] block1 = __NSGlobalBlock__ 2019-06-24 21:13:14.555444+0800 Block[30548:1189724] block2 = __NSGlobalBlock__ 2019-06-24 21:13:14.555465+0800 Block[30548:1189724] block3 = __NSStackBlock__ */ } return 0; } 复制代码
针对各类不一样的block
总结以下
block 类型 |
环境 |
---|---|
__NSGlobalBlock__ |
没有访问auto变量 |
__NSStackBlock__ |
访问了auto变量 |
__NSMallocBlock__ |
__NSStackBlock__ 调用了copy |
__NSMallocBlock__
是放在堆区域__NSMallocBlock__
类型的block
, 咱们能够调用copy
方法void (^block3)(void) = ^(void){ NSLog(@"age = %d", weight); }; NSLog(@"block3 = %@", [block3 class]); NSLog(@"block3 = %@", [[block3 copy] class]); /* 输出分别是: block3 = __NSStackBlock__ block3 = __NSMallocBlock__ */ 复制代码
__NSStackBlock__
类型的block
调用copy
方法后, 就会变成__NSMallocBlock__
类型的block
block
是在堆区域的copy
方法后,又会如何? 下面一块儿来看一下吧int weight = 21; void (^block1)(void) = ^(void){ NSLog(@"Hello World"); }; void (^block3)(void) = ^(void){ NSLog(@"age = %d", weight); }; NSLog(@"block1 = %@", [block1 class]); NSLog(@"block1 = %@", [[block1 copy] class]); NSLog(@"block3 = %@", [block3 class]); NSLog(@"block3 = %@", [[block3 copy] class]); NSLog(@"block3 = %@", [[[block3 copy] copy] class]); /* __NSGlobalBlock__ __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__ __NSMallocBlock__ */ 复制代码
__NSStackBlock__
类型的block
调用copy
以后才会变成__NSMallocBlock__
类型, 其余的都是原类型__NSStackBlock__
类型的做用域是在栈中, 做用域中的局部变量会在函数结束时自动销毁__NSStackBlock__
调用copy
操做后,分配的内存地址至关于从栈复制到堆;副本存储位置是堆Block类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
__NSStackBlock__ |
栈 | 从栈复制到堆 |
__NSGlobalBlock__ |
程序的数据区域 | 什么也不作 |
__NSMallocBlock__ |
堆 | 引用计数增长 |
ARC
环境下, 编译器会根据状况自动将站上的block
复制到堆上, 相似如下状况
block
做为函数返回值时block
赋值给__strong
修饰的指针时block
做为GCD
的方法参数时Question: 定义一个auto修饰的局部变量, 并在block
中修改该变量的值, 可否修改为功呢?
auto int width = 10; static int height = 20; void (^block)(void) = ^(void){ // 事实证实, 在Xcode中这行代码是报错的 width = 22; // 可是static修饰的变量, 倒是能够赋值, 不会报错 height = 22; NSLog(@"width = %d, height = %d", width, height); }; block(); // width = 10, height = 22 复制代码
block
中, auto
修饰的变量是值传递static
修饰的变量是指针传递, 因此在上述代码中, block
存储的只是height
的内存地址auto
变量实在main
函数中定义的, 而block
的执行逻辑是在__main_block_func_0
结构体的方法中执行的, 至关于局部变量不能跨函数访问static
修饰的变量为何能够修改?
__main_block_impl_0
结构体中height
存储的是其内存地址, 在其余函数或者结构体中访问和改变height
的方式都是经过其真真访问的(*height) = 22;
(*height)
__block auto int width = 10; void (^block)(void) = ^(void) { // 很明显, 这里就能够修改了 width = 12; NSLog(@"width = %d", width); }; block(); // width = 12 复制代码
为何上面的代码就能够修改变量了呢, 这是为何呢.....请看源码
下面是生成的block
的结构体
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // 这里的width被包装成了一个__Block_byref_width_0对象 __Block_byref_width_0 *width; // by ref // 这里能够对比一下以前的未被__block修饰的int变量 // int width; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_width_0 *_width, int flags=0) : width(_width->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 复制代码
__block
能够用于解决block
内部没法修改auto
修饰的变量值得问题__block
不能修饰全局变量和static
修饰的静态变量(一样也不须要, 由于在block
内部能够直接修改)__block
修饰的变量会被包装成一个对象(__Block_byref_width_0
)width
被包装后的对象的结构体, 在结构体内, 会有一个width
成员变量struct __Block_byref_width_0 { void *__isa; // 一个指向本身自己的成员变量 __Block_byref_width_0 *__forwarding; int __flags; int __size; // 外部定义的auto变量 int width; }; 复制代码
下面咱们先看一下, auto
和block
的定义和调用
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // __block auto int width = 10; auto __Block_byref_width_0 width = { 0, &width, 0, sizeof(__Block_byref_width_0), 10 }; void (*block)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, &width, 570425344 ); block->FuncPtr(block); } return 0; } 复制代码
__Block_byref_width_0
类型的width
中的每个参数分别赋值给了__Block_byref_width_0
结构体中的每个成员变量block
内部从新对width
从新赋值的逻辑中static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_width_0 *width = __cself->width; // bound by ref (width->__forwarding->width) = 12; NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_9241d5_mi_0, (width->__forwarding->width)); } 复制代码
width
是一个__Block_byref_width_0
类型的变量width
对象经过找到内部的__forwarding
成员变量__Block_byref_width_0
结构体中__forwarding
是一个指向本身自己的成员变量__forwarding
找到__Block_byref_width_0
的成员变量width
, 在进行从新赋值NSLog
中也是经过这种逻辑获取width
的值欢迎您扫一扫下面的微信公众号,订阅个人博客!