Block的实质

Block是“带有自动变量值的匿名函数”。

通过clang -rewirte-objc 源代码文件名就能将含有Block语法的源代码变换为cpp的源代码。

int main(int argc, char * argv[]) {void (^blk)(void) = ^{printf("Block");};blk();return 0;}
复制代码

变换后截取其中的代码逻辑部分如下:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};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");}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, 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;}复制代码

首先为:

^{printf("Block");};
复制代码

变换后为:

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{printf("Block");
}
复制代码

通过Block使用的匿名函数实际上被作为简单的C语言函数来处理。 另外,根据Blocl语法所属的函数名(此处为main)和该Block语法在函数出现的顺序值(此处为0)来给经clang变换的函数命名。 该函数的参数__cself相当于C++实例方法中志向实例自身的变量this,或者OC中的self,即参数__cself为志向Block值的变量。

这个方法中参数的声明为:

struct __main_block_impl_0 *__cself
复制代码

参数__cself是__main_block_impl_0结构体的指针。

该结构体声明如下:

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;}
};复制代码

去除构造函数的部分为:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;
};复制代码

再看__block_impl结构体的声明:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};
复制代码

里面包含某些标志、今后版本升级所需的区域以及函数指针。 第二个成员变量是Desc指针,以下为__main_block_desc_0结构体的声明。

static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
}
复制代码

其结构为今后版本升级所需要的区域和Block的大小。

再看__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;}
复制代码

以上就是初始化__main_block_impl_0结构体成员的源代码。

再看看main函数中构造函数的调用如下:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
复制代码

去掉转换部分来看如下:

__main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0,&__main_block_imp_0_DATA);struct __main_block_impl_0 *blk = &temp;
复制代码

该源代码将__main_block_impl_0结构体的自动变量,即栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。

以下这句代码代表最初的打印代码:

void (^blk)(void) = ^{printf("Block");};
复制代码

将打印的Block块赋给Block类型变量blk,相当于将__main_block_impl_0结构体的指针赋值给变量blk。打印的代码块就是__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0的结构体实例。

再来看__main_block_impl_0实例的构造方法参数:

__main_block_impl_0(__main_block_func_0,&__main_block_imp_0_DATA);
复制代码

第一个参数是由Block语法转换C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。

下面是__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)};
复制代码

由上部分代码可以,该源代码使用Block,即__main_block_impl_0结构体实例的大小,进行初始化。

再来看__main_block_impl_0结构体

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;}
};struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};复制代码

以上,__main_block_impl_0结构体等同于,结构体构造函数会如下进行初始化:

struct __main_block_impl_0 {void *isa;int Flags;int Reserved;void *FuncPtr;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;}
};复制代码

那么调用打印代码块的部分应该为blk();

就可以变换为以下代码:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);复制代码

去掉转换部分后:

(*blk->impl.FuncPtr)(blk);
复制代码

这是简单地使用函数指针调用函数。由打印代码块转换的__main_block_func_0函数的指针被赋值到结构体的成员变量FuncPtr中,也说明了__main_block_func_0的参数__cself指向Block值。

但是impl.isa = &_NSConcreteStackBlock;,将Block指针赋值给Block结构体成员变量isa。

注:isa为何物?

引用简书作者曲年_《Objective-C isa 指针 与 runtime 机制》_一文中解释如下

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。

那么什么是类呢?在xcode中用快捷键Shift+Cmd+O 打开文件objc.h 能看到类的定义:

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;/// Represents an instance of a class.
struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
复制代码

可以看出: Class是一个objc_class结构类型的指针,id是一个objc_object结构类型的指针。

objc_class结构体的定义如下:

struct objc_class {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class _Nullable super_class                              OBJC2_UNAVAILABLE;const char * _Nonnull name                               OBJC2_UNAVAILABLE;long version                                             OBJC2_UNAVAILABLE;long info                                                OBJC2_UNAVAILABLE;long instance_size                                       OBJC2_UNAVAILABLE;struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */复制代码

各参数含义如下:

  • isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
  • super_class:父类,如果该类已经是最顶层的根类,那么它为NULL。
  • version:类的版本信息,默认为0
  • info:供运行期使用的一些位标识。
  • instance_size:该类的实例变量大小
  • ivars:成员变量的数组

每一个对象本质上都是一个类的实例。其中类定义了成员变量和成员方法的列表。对象通过对象的isa指针指向类。

每一个类本质上都是一个对象,类其实是元类(meteClass)的实例。元类定义了类方法的列表。类通过类的isa指针指向元类。

所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环。

objc_class结构体与objc_object结构体相同。但是,objc_object结构题是各个对象在实现中使用的最基本的结构体,objc_class是类在视线中使用的最基本的结构体。

例:

@interface MyObject:NSObject
{int val0;int val1;
}
复制代码

基于objc_object结构体,该类的对象的结构体如下:

struct MyObject{Class isa;int val0;int val1;
}复制代码

MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。 OC中由各类生成对象意味着,像该结构体这样“生成由该类生成的对象的结构体实例”。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。


截获自动变量值

int main(int argc, char * argv[]) {int dmy = 256;int val = 10;const char *fmt = "val = %d\n";void (^blk)(void)=^{printf(fmt,val);};val = 2;fmt = "These values were changed.val=%d\n";blk();return 0;
}
复制代码

通过clang -rewrite-objc转换后的代码如下:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;int val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {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 copyint val = __cself->val; // bound by copyprintf(fmt,val);}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, char * argv[]) {int dmy = 256;int val = 10;const char *fmt = "val = %d\n";void (*blk)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));val = 2;fmt = "These values were changed.val=%d\n";((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;}复制代码

有区别的部分仅仅在于以下部分:将变量作为成员变量追加到了__main_block_impl_0 结构体中。

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;int val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};复制代码

__main_block_impl_0结构体内声明的成员变量了行于自动变量类型完全相同。但是Block语法表达式中没有使用的自动变量不会被追加。Blocks的自动变量截获只针对Block中使用的自动变量。

在初始化结构体实例是,根据传递给构造函数的参数对由自动变量追加的成员变量进行初始化。

__main_block_impl_0初始化的代码过程总结来说就是以下赋值过程:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = &__main_block_desc_0_DATA;
val = 10;
fmt = "val = %d\n";复制代码

由此可知,在__main_block_impl_0结构体实例中(即打印代码块),自动变量被截获。

再来看其中匿名函数代码块:

^{printf(fmt,val);
};复制代码

可以转换为:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt = __cself->fmt; // bound by copyint val = __cself->val; // bound by copyprintf(fmt,val);}
复制代码

在转换后的代码中,截获到__main_block_impl_0结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义。因此,原来的源代码表达式无需改动遍可以使用截获的自动变量值执行。

总的来说,所谓“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构实例中。

Objective-C 之Block(2)相关推荐

  1. 唐巧的iOS技术博客选摘

    1. 那些被遗漏的objective-c保留字:http://blog.devtang.com/blog/2013/04/29/the-missing-objc-keywords/ 2. 使用cras ...

  2. HCIE-Cloud笔试

    前言: 目录按照HCIE-Cloud官方培训教材V2.0进行制定,通过笔试考点挂钩HCIE-Cloud官方培训教材V2.0中的知识,相互强化记忆 1.优点:基本笔试的知识分类,是按照该教材的目录大纲分 ...

  3. 《黑马程序员》 block的使用(Objective - c语法)

    ------- <a href="http://www.itheima.com" target="blank">android培训</a> ...

  4. 如何在Objective C中声明Block?

    本文翻译自http://fuckingblocksyntax.com/ 作为局部变量: returnType(^blockName)(parameterTypes) = ^returnType(par ...

  5. iOS完全自学手册——[三]Objective-C语言速成,利用Objective-C创建自己的对象...

    1.前言 上一篇已经介绍了App Delegate.View Controller的基本概念,除此之外,分别利用storyboard和纯代码创建了第一个Xcode的工程,并对不同方式搭建项目进行了比较 ...

  6. Objective C ARC 使用及原理

    Objective C ARC 使用及原理 手把手教你ARC ,里面介绍了ARC的一些特性, 还有将非ARC工程转换成ARC工程的方法 ARC 苹果官方文档 下面用我自己的话介绍一下ARC,并将看文档 ...

  7. iOS探索:Block解析浅谈

    什么是Block Block是将函数及其执行上下文封装起来的对象 接下来让我们通过源码来看一看Block的本质 我们在一个方法中写了三行代码,第一行是定义了一个局部变量,第二行是一个Block,第三行 ...

  8. block在美团iOS的实践

    说到block,相信大部分iOS开发者都会想到retain cycle或是__block修饰的变量. 但是本文将忽略这些老生常谈的讨论,而是将重点放在美团iOS在实践中对block的应用,希望能对同行 ...

  9. 睡眠排序法-objective C版的代码

    将开发过程比较重要的代码做个珍藏,下面代码内容是关于睡眠排序法-objective C版的代码,应该能对各位朋友有帮助. @interface NSArray (SleepSort) - (void) ...

  10. Objective C范型

    范型 范型编程是一种程序语言设计范式,它允许程序员在使用强类型的语言编写代码的时候,延迟确定具体的类型. 以Swift代码为例,假如有一个需求是要交换两个int,很容易写出类似代码 func swap ...

最新文章

  1. 压缩目录Linux常用的几种文件压缩解压方法
  2. 解决SecureCRT与SecureFX中文乱码问题
  3. fopen文件路径怎么写_PHP文件上传
  4. oracle 查询数据库表空间大小和剩余空间
  5. Docker 之MySQL 重启,提示Error response from daemon: driver failed programming external connectivity on **
  6. 汇编语言程序设计 实验6 分支程序设计
  7. inspinia admin 最新版 inspinia 2.7.1 一套非常优秀的bootstrap后台管理模板
  8. Mac 安装 MAT内存分析工具
  9. YOLOv3 SPP源码分析
  10. 在ArcMap中制作Python加载项点击小班依次编号
  11. PCB、SCH转化为AD工程
  12. 何恺明目前的学术成果是否够得上计算机视觉领域历史第一人?
  13. Actor模型的优缺点
  14. Jenkins 插件开发记录
  15. xcode 工程常见问题
  16. 学习笔记:计算excel中的平均值并去除0值
  17. 教程-mac版本texpad配置中文环境教程
  18. Ubuntu(Linux)系统截图软件推荐flameshot
  19. 学习 Google Gadgets (一)
  20. Android 7 Nougat 源码目录结构

热门文章

  1. DevExpress的TreeList怎样设置数据源,从实例入手
  2. RocketMQ-什么是死信队列?怎么解决
  3. 用户识别率提升 25 倍 | 看神策数据如何利用 ID-Mapping 激活全域营销
  4. 数字化在金融领域的应用与实践,从“我觉得”到“用户觉得”
  5. 直播报名 | 券商如何精细化运营?
  6. HashMap根据value值排序
  7. 使用SpringBoot Actuator监控应用
  8. 云效助力新金融DevOps转型——南京银行实践之路
  9. iOS:后台定位并实时向服务器发送位置
  10. Huffman(哈夫曼)编码--又称最佳编码(最有效的二进制编码)