Block学习总结(二)

block

1.block的实质

之前说过其实block的本质就是"带有自动变量的匿名函数"。block类型的变量与函数指针类似,仅是将 * 号换成了 ^ 符号.那么block的底层实现又是如何。 这里可以通过 clang命令将block语法转换成底层的c语言。具体的实现: 我们可以打开终端,进入想要转换的block文件目录下,执行"clang -rewrite-objc 文件名"指令。至此就能将原文件转换成.cpp的底层文件了。

void test() {
    void(^blk)() = ^(){
        printf("is block");
    };
    blk();
}

以上的代码通过clang 指令后,转换成以下的源码。(这里指摘取出与block语法相关的代码)

 struct __block_impl {
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
 };
 
 static struct __test_block_desc_0 {
 size_t reserved;
 size_t Block_size;
 }
 __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)
 
 };
 
 struct __test_block_impl_0 {
 
 struct __block_impl impl;
 struct __test_block_desc_0* Desc;
 
 __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int flags=0) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
 
 };
 
 static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
 
 printf("is block");
 }

 void test() {
 
 void(*blk)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA));
 
 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
 }

首先,看到原来的test 函数转换成了

//源代码
void test() {
 //将block函数赋值给变量blk
    void(^blk)() = ^(){
        printf("is block");
    };
 //执行block
    blk();
}

<===>

//转换后的代码
void test() {
 
 //将block函数赋值给变量blk
 void(*blk)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA));
 
 //执行block
 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
 }
 
 <===>
 //为了更好的滤清代码,去掉类型的强转
 //去除强转后的代码
void test() {

 //将block函数赋值给变量blk
 *blk = & __test_block_impl_0(*__test_block_func_0, &__test_block_desc_0_DATA);
 
 //执行block
 blk->FuncPtr(blk);

 }

先看 将block函数赋值给变量blk 这段代码,它的实质是将 " _ test_block_impl_0类型的实例的指针”赋值给了 “ test_block_impl_0指针类型的变量blk”。 而 _ _test_block_impl_0 是一个结构体。

__test_block_impl_0结构体

为什么这个结构体的名称叫 __test_block_impl_0,它的命名规则是 “ _ _ _block所在的函数的名字block_impl第几个block ”。

void test1() {
    void(^blk)() = ^(){
        printf("is block");
    };
    blk();
    
    void(^blk2)() = ^(){
        printf("is block2");
    };
    blk2();
}

则结构体为

__test1_block_impl_0

__test1_block_impl_1

接下来看看 __test_block_impl_0结构体具体的构造

 struct __test_block_impl_0 {
 
 struct __block_impl impl; //成员变量 impl
 struct __test_block_desc_0* Desc; // 成员变量 Desc
 
//结构体的构造方法
 __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int flags=0) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
 
 };

它由2个成员变量和一个构造函数组成. 第一个成员变量impl是一个 __block_impl类型的结构体

 struct __block_impl {
 void *isa; // isa 指针
 int Flags; //标记 Flags
 int Reserved;
 void *FuncPtr; //函数指针 FuncPtr
 };
 

第二个成员变量desc 是一个指向 " _ _test_block_desc_0" 结构体的指针。并且定义了一个名为__test_block_desc_0_DATA 的变量(初始化block时用到)。

 static struct __test_block_desc_0 {
 size_t reserved;
 size_t Block_size; //block大小
 }
 
 __test_block_desc_0_DATA = { 
 0, 
 sizeof(struct __test_block_impl_0) // __test_block_impl_0大小
 };
 

再来看下源代码中初始化结构体的函数,将参数带入到结构体构造函数后

& __test_block_impl_0(*__test_block_func_0, &__test_block_desc_0_DATA);

<===>

 __test_block_impl_0(*__test_block_func_0, &__test_block_desc_0_DATA, int flags=0) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = __test_block_func_0;
 Desc = __test_block_desc_0_DATA;
 }

这里还需要解释一个 _test_block_func_0 的变量,它的本质是一个普通的函数,命名规则 " _ _block所在的函数名block_func第几个block"。其实它就是原block的执行部分。

 static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
 printf("is block");
 }
 
 <===>
 相当于原block函数的执行部分
 ^(){
        printf("is block");
    };

参数 _ _cself 是block本事,相当于 oc 函数中的self。(在改block中还没有用到__cself)

block的调用

最后就是执行block,通过上面的分析我们其实就知道了block的调用就是通过函数指针执行block函数。并将block本事作为参数传入。

 blk->FuncPtr(blk);
 <===>
 blk.impl.FuncPtr = tempFuncptr;
 *tempFuncptr = &__test_block_func_0;
 __test_block_func_0(blk);

2.引用外部变量的实质

当block引用了外部变量的时候,它与没有引用外部变量的时候,通过clang转换的源码来看是有不同的。例如,下面的这段引用了外部变量的block

void testBlock2() {    
    int a = 10;
    int b = 20;
    void(^blk)() = ^(){
        printf("a = %d",a);
    };
    blk();
}

通过 clang指令,它将转换成了如下的代码

 <=== 以下代码与未使用外部变量有区别 ===>
 void testBlock2()
 int a = 10;
 int b = 20;
 void(*blk)() = ((void (*)())&__testBlock2_block_impl_0((void *)__testBlock2_block_func_0, &__testBlock2_block_desc_0_DATA, a));
 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
 }
 
 struct __testBlock2_block_impl_0 {
 struct __block_impl impl;
 struct __testBlock2_block_desc_0* Desc;
 int a;
 __testBlock2_block_impl_0(void *fp, struct __testBlock2_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
 };

 static void __testBlock2_block_func_0(struct __testBlock2_block_impl_0 *__cself) {
 int a = __cself->a; // bound by copy
 printf("a = %d",a);
 }
 
<=== 以下代码与未使用外部变量相同 ===>
 struct __block_impl {
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
 };
 
 static struct __testBlock2_block_desc_0 {
 size_t reserved;
 size_t Block_size;
 } __testBlock2_block_desc_0_DATA = { 0, sizeof(struct __testBlock2_block_impl_0)};

来对比一下与未使用外部变量时区别的部分,首先是__testBlock2_block_impl_0这个结构体,它多出了一个与引用的外部变量相同类型的成员变量。(这里只添加了block内部使用了的外部变量a这一个成员变量,外部变量b没有使用,所以没有添加到结构体中):

 struct __testBlock2_block_impl_0 {
 struct __block_impl impl;
 struct __testBlock2_block_desc_0* Desc;
 int a;  //引用的外部变量 追加成了 __testBlock2_block_impl_0 结构体的成员变量
 };

再来看一下__testBlock2_block_impl_0实例初始化的方法:

 __testBlock2_block_impl_0(void *fp, struct __testBlock2_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }

这个初始化方法与未使用外部变量时的区别是多了一个参数a,而: a(_a)这个语法是c++中结构体中给const类型的变量初始化的意思。即将参数_a赋值给结构体的变量a。

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

推荐阅读更多精彩内容