Block笔记(二)

上一篇我们了解了一下Block的特性,用法,概念等一些Block的基本使用方法,这一篇我们来向更深处挖掘一下,探究一下Block的实现。

Block的实质

上一篇讲到的Block的语法看上去好像很特别,但它实际上是作为极普通的C语言源代码来处理的。通过支持Block的编译器,含有Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言代码被编译

Clang(LLVM编译器)具有将代码转换为我可读源代码的功能。通过“-rewrite-objc”选项就能将含有Block语法的源代码变换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。

clang -rewrite-objc 源代码文件名

下面我们转换Block语法。

int main() {

    void (^block)(void) = ^{
        printf("Block\n");
    };
    
    block();
    
    return 0;
}

这是一个最简单的Block语法,它省略了返回值类型以及参数列表,该源代码通过clang可变换为如下形式(为便于理解有删减)

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\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() {
    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;
}

乍一看完全没有看下去的想法对不对,8行的源代码增加到了四十多行,我们将其拆解分成几个部分理解,多看几次相信就会理解其中原理了,首先我们来看最初源代码中的Block语法。

^{
   printf("Block\n");
 };

我们再看它变换后的源代码。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("Block\n");
}

Block被作为最简单的C语言函数来处理,根据语法所属的函数名(也就是main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。

该函数的参数_cself相当于C++实例方法中指向实例自身变量this,或是Objective-C实例方法中指向对象自身的变量self,即参数_cself指向Block值的变量。

struct __main_block_impl_0 *__cself

之前说C++中的thisObjective-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结构体看起来是不是非常简单。第一个成员变量是impl,我们再来看一下其__block_impl结构体的声明。

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

看了这个结构体的成员变量isa,Flags标志,版本升级所需的区域,函数指针,这些我们会在后面详细说明,我们再来看一下__main_block_impl_0结构体的第二个成员变量Desc,以下为其struct __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;
  }

构造函数使用了之前我们之前说明的__block_impl结构体以及struct __main_block_desc_0结构体来初始化__main_block_impl_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结构体类型的自动变量tmp,即__main_block_impl_0结构体实例指针,赋值给__main_block_impl_0结构体指针类型的变量block

以下这部分代码对应最初源代码。

 void (^block)(void) = ^{
        printf("Block\n");
    };

将Block语法生成的Block赋值给Block类型变量block。它等同于将__main_block_impl_0结构体实例指针赋值给变量block。该源代码中的Block就是__main_block_impl_0结构体类型的自动变量,即__main_block_impl_0结构体实例

下面再来看看__main_block_impl_0结构体实例构造参数

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

__main_block_func_0是由Block语法转换的C语言函数指针__main_block_desc_0_DATA是作为静态全局变量初始化的__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)};

由此可以看出该源代码使用__main_block_impl_0结构体实例的大小进行初始化。

好了上面我们将整个__main_block_impl_0结构体的内容拆开分解研究了一遍,现在我们将他们组合起来看看,这个__main_block_impl_0是究竟如何进行的初始化。我们将__main_block_impl_0结构体的_block_impl结构体也展开,可记述为如下形式:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  struct __main_block_desc_0* Desc;
};

然后通过这个结构体的构造函数会进行如下初始化:

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

__main_block_func_0函数指针被赋值给成员变量FuncPtr,我们再看看使用Block的部分

block();

此部分经过转换的源代码如下:


((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

我们再将其简化一下,简化后如下:

(*block->impl.FuncPtr)(block);

这就是最简单的使用函数指针调用函数,由Block语法转换的__main_block_func_0函数的指针被赋值在成员变量FuncPtr中,这也说明了__main_block_func_0函数的参数__cself指向Block的值,在调用该函数的源代码中可以看出Block正是作为参数进行了传递。

到这里我们总算摸清楚了Block实现的原理,现在我们再来说说上文跳过的__block_impl结构体,这个结构体的第一个成员变量叫做isa,是不是似曾相识,Objective-C中对象的实质是不是就是一个objc_object的结构体,在Objective-C中由类生成对象,而类本身也是一个对象。

objc_object的结构体中也有一个成员变量叫做isa,而通过这个isa我们可以访问生成objc_object的类objc_class,而objc_class中也有一个isa用于访问其元类。这部分内容大家可以去看我的另一篇简书文章iOS中的class,SEL,IMP,由此我们便可以知道所谓Block就是Objective-C对象。

__main_block_impl_0结构体就相当于基于objc_object结构体的Objective-C类对象的结构体,__block_impl便相当于objc_class__main_block_impl_0初始化时的isa = &_NSConcreteStackBlock,即_NSConcreteStackBlock相当于objc_class的实例,将Block当作对象处理时,关于该类的信息就放置于_NSConcreteStackBlock中。

以上就是Block的实质,理解起来可能稍微有些复杂,一定要将整体的源码拆解开来分部理解,多看几次,并且理解iOS中类与对象的实质,这对于理解Block也很有帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 916评论 1 3
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,755评论 5 61
  • Block 的实质 Block 是“带有自动变量值的匿名函数”,我们可以通过 Block 的实现来加深理解。首先通...
    yanging阅读 343评论 0 0
  • 摘要block是2010年WWDC苹果为Objective-C提供的一个新特性,它为我们开发提供了便利,比如GCD...
    西门吹雪123阅读 907评论 0 4
  • 这个周六的上午我和闺蜜带着孩子们去福利院看望那些残障小孩。一是献份爱心给他们带去温暖,二是让我们的孩子们接受不一样...
    鸢尾在歌唱阅读 200评论 0 0