iOS 原理探索-Block(一)
2020/3/8 23:01:43
本文主要是介绍iOS 原理探索-Block(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Block是C语言的扩充功能。用一句话来形容Block的扩展功能:带有自动变量(局部变量)的匿名函数。
Block概述
Block类型变量
Block语法单从其记述方式上来看,除了没有名称以及带有**“^”外,其他的都与C语言的函数定义相同。在C**语言定义函数时,就可以将函数的地址赋值给函数指针类型的变量。
int function(int count) { return count+1; } int (* functionptr)(int) = &function; 复制代码
声明Block类型的变量的示例如下:
int (^block)(int par); 复制代码
通过和函数指针的对比可以知道,声明Block类型的变量仅仅是将函数指针类型变量的**"*"修改为^,所以该Block类型的变量与一般的C**语言变量完全相同,可以作为以下用途使用。
-
自动变量(局部变量)。
-
函数参数。
-
静态变量。
-
静态全局变量。
-
全局变量(属性)。
接下来我们定义一个Block并将其赋值给Block类型的变量。
int (^block)(int) = ^(int count){ return count+1; }; 复制代码
由**^开始的Block语法生成的Block被赋值给变量block中,因为和普通变量相同,所以当然也可以由一个Block类型的变量向另一个Block**类型的变量赋值。
int (^block1)(int) = block; int (^block2)(int); block2 = block1; 复制代码
另外我们也可以在函数的参数和函数的返回值中使用Block。
通过上述我们可以看出,在声明Block类型的变量和用Block作为函数的参数和返回值中,记述的方式及其复杂。这是我们可以像使用函数指针那样使用typedef来解决问题。
typedef int (^block_t)(int); 复制代码
原来的声明方式
int (^block)(int); 复制代码
现在的声明方式
block_t blk; 复制代码
通过使用typedef,Block类型的变量的定义变得更简单了。
捕获局部变量的值
int var = 10; const char *fmt = "var = %d\n"; void(^block)(void) = ^(){ printf(fmt, var); }; var = 2; fmt = "These value ware change. val=%d."; block(); 复制代码
上述代码运行后的结果。
var = 10 复制代码
执行的结果并不是修改之后的值,而是Block自动捕获的值。
Block实现
Block的实质
通过**“clang -rewrite-objc ”将含有Block语法的代码转换为C++代码。说是C++,其实也就是使用了struct而已,其本质是C**语言代码。
int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... void (^block)(void) = ^(){ printf("block\n"); }; block(); } return 0; } 复制代码
接下来我们通过clang将这份代码转换为**C++**的形式。
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) { printf("block\n"); } 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; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } 复制代码
接下来我们来逐步分析这段代码。
首先我们可以看到转换后的源代码也有着相同的表达式。
__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("block\n"); } 复制代码
如转换后的代码所示,使用Block使用的匿名函数,实际上被当做C语言中的简单函数处理。
该函数参数中的**__cself相当于OC实例方法中指向自身的实例变量self**,即参数**__cself为指向Block**值的变量。
struct __main_block_impl_0 *__cself 复制代码
__main_block_impl_0
和OC中的self相同,参数**__cself是__main_block_impl_0**结构体的指针。该结构体声明如下:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; }; 复制代码
由于转换后的代码一并写入了构造函数(可以简单的认为是OC中的初始化方法)。在这里我们将其去掉,那么看起来将会十分简单。第一个成员变量为impl,我们看下impl的声明。
__block_impl
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; 复制代码
__main_block_desc_0
第二个成员变量是Desc指针,以下为其**__main_block_desc_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)}; 复制代码
__main_block_impl_0构造函数
接下来我们来看看**__main_block_impl_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; } 复制代码
当前这个构造函数接收参数:
- fp:是由Block语法转换的C语言函数指针,也就是我们外界传入的**__main_block_impl_0的C**语言函数。
- desc:类型为**__main_block_desc_0**结构体的实例。
接下来我们来看看改构造函数的调用:
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 复制代码
由于转化后的代码较长,所以我们去掉了一部分,具体如下:
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); struct __main_block_impl_0 *block = &tmp; 复制代码
这样就容易理解了,该源代码是将**__main_block_impl_0类型的局部变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量block**,以下为这部分源代码对应的最初源代码。
void (^block)(void) = ^(){ printf("block\n"); }; 复制代码
由此可以该源代码使用的block变量,是由**__main_block_impl_0**结构体实例的大小,来进行初始化的。
Block的调用
block(); 复制代码
这部分代码被转换成了如下部分:
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); 复制代码
去掉类型强转部分:
(*block->impl.FuncPtr)(block); 复制代码
这就是使用了函数指针来调用函数,由Block语法转换的**__main_block_func_0的函数的指针赋值给了成员变量impl的成员变量FuncPtr**。
__main_block_func_0的参数**__cself指向Block的值(也就是实例)。在函数调用的源代码中,我们可以看到Block**正是作为参数传递的。
_NSConcreteStackBlock
在我们的**__main_block_impl_0**的构造函数中我们看到这样一句代码。
impl.isa = &_NSConcreteStackBlock; 复制代码
看到这句代码时,是不是觉得和我们OC中类的结构很相似。所以说把Block作为OC对象处理时关于该类的信息都会被放置于**_NSConcreteStackBlock**中。
所以说Block其实就是OC对象。
自动捕获局部变量的值
构造函数探索
int var = 10; const char *fmt = "var = %d\n"; void (^block)(void) = ^(){ printf(fmt, var); }; 复制代码
和之前一样,我们将这段代码通过clang编译后来进行研究:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; const char *fmt; int var; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _var, int flags=0) : fmt(_fmt), var(_var) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char *fmt = __cself->fmt; // bound by copy int var = __cself->var; // bound by copy printf(fmt, var); } 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_impl_0**结构体中。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; const char *fmt; int var; }; 复制代码
__main_block_impl_0结构体中声明的成员变量的类型和局部变量的类型完全相同。
注意:Block代码块中没有使用到的局部变量不会被自动追加。Block只捕获在其代码块中使用到的局部变量。
接下来我们来看看初始化该结构体实例的构造函数之间的差异:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _var, int flags=0) : fmt(_fmt), var(_var) 复制代码
在初始化结构体实例的同时,根据传递给构造函数的参数对局部变量追加的成员变量进行初始化。
: fmt(_fmt), var(_var) 复制代码
这句代码就是对结构体中自动追加的成员变量的初始化。
匿名函数
接下来我们来看看使用Block的匿名函数的实现,最初的源代码的Block语法的实现如下:
^(){ printf(fmt, var); } 复制代码
该源代码被转换成了以下函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char *fmt = __cself->fmt; // bound by copy int var = __cself->var; // bound by copy printf(fmt, var); } 复制代码
总的来说,所谓的自动捕获局部变量就是在执行Block时,Block代码块使用到的局部变量被保存到了Block的结构体实例(也就是Block自身)中。
__block修饰符
众所周知当我们的局部变量没有任何修饰符的时候,是不能在Block的代码块中被修改的,如果对局部变量执行了修改操作,那么它将会报出一个编译时错误。
Variable is not assignable (missing __block type specifier) 复制代码
解决这个问题有两种办法。第一种:使用下面几种类型的变量。
- 静态变量
- 静态全局变量
- 全局变量
接下来我们来看看这段代码:
int global_var = 1; static int static_global_var = 2; int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... static int static_val = 3; void (^block)(void) = ^(){ global_var += 1; static_global_var += 2; static_val += 3; }; block(); } return 0; } 复制代码
该代码在Block代码块的内部改变了global_var、static_global_var、static_val三个变量的值。该源代码转换后的代码如下:
int global_var = 1; static int static_global_var = 2; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *static_val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) { 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; // bound by copy global_var += 1; static_global_var += 2; (*static_val) += 3; } 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)}; 复制代码
对静态全局变量和全局变量的访问与转换前相同,那么局部静态变量又要如何转换呢?
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_val = __cself->static_val; // bound by copy global_var += 1; static_global_var += 2; (*static_val) += 3; } 复制代码
使用静态变量static_val的指针对其进行访问,将静态变量static_val的指针传递给**__main_block_impl_0**结构体的构造函数并保存。这是超出作用域使用变量最简单的办法。
第二种方法是使用**__block**:
接下来我们就使用**__block**来修饰我们的局部变量:
int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... __block int var = 10; void (^block)(void) = ^(){ var = 1; }; block(); } return 0; } 复制代码
该源代码转换后的代码如下:
struct __Block_byref_var_0 { void *__isa; __Block_byref_var_0 *__forwarding; int __flags; int __size; int var; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_var_0 *var; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__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_var_0 *var = __cself->var; // bound by ref (var->__forwarding->var) = 1; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->var, 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}; 复制代码
只是增加了**__block**代码量就急剧增加,我们来看看它是如何转换的。
__block int var = 10; 复制代码
转换后的代码:
__Block_byref_var_0 var = { 0, &var, 0, sizeof(__Block_byref_var_0), 10 }; 复制代码
我们发现被**__block**修饰的变量被转换成了结构体实例,这个实例存储在栈区。
__Block_byref_var_0结构体声明如下:
struct __Block_byref_var_0 { void *__isa; __Block_byref_var_0 *__forwarding; int __flags; int __size; int var; }; 复制代码
该结构体的最后一个成员变量var就相当于原来的局部变量var。
那么下面这段个**__block**修饰的变量赋值时的情况怎么样呢?
^(){ var = 1; }; 复制代码
该源代码的转换如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_var_0 *var = __cself->var; // bound by ref (var->__forwarding->var) = 1; } 复制代码
__Block_byref_var_0结构体实例的成员变量**__forwarding持有指向该实例自身的指针,通过成员变量__forwarding访问成员变量var**。(成员变量var是该实例自身持有的变量,相当于原来的局部变量var)。
如下图所示:
总结
- Block其实就是OC对象。
- Block会自动捕获局部变量,全局变量不会捕获。
- 如果要在Block的代码块里修改变量的值,有两种方案:
-
- 使用全局静态变量、静态变量、全局变量。
-
- 使用__block修饰符来修饰局部变量。
这篇关于iOS 原理探索-Block(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值