详细的探讨一下Block(讨论篇、基础篇、实质篇)

章节目录

  • 关于Block的讨论篇
  • Block的基础篇
  • Block的实质篇
  • 讨论篇:

为什么要看Block?
  1. 为了更熟练地使用Block
  • 为了扩展程序编程的思维

  • 了解底层的运作原理,有助于与他人探讨相关问题

  • 公认的大神中基本都会,作为一个菜鸟要多向大牛学习

  • 很多面试都需要

  • 基础篇:

什么是block?

Block 一个带有自动变量的匿名函数。
匿名函数是因为Block没有函数名称,由^ 返回值类型 入参类型 表达式 组成。但可以赋值给Block类型变量。
自动变量在Block中表现为截获自动变量值,指Block内部调用外部变量时会捕获该变量在此瞬间的值。

Block长什么样?

通常: ^ 返回值类型 参数类型 表达式,如: ^int(int count){return count + 1; }
但无论是整型还是无返回值,返回值类型都可省略为: ^ 参数列表 表达式
省略返回值,根据表达式中有无return决定是否有返回值,如果有则返回该返回值类型,如果没有就是无返回值。所以,所有return返回值类型都必须相同。
如:^(int count){return count + 1;}
若没有参数,则参数类型也可省略,简化为:表达式。如:{NSLog(@“DrunkenMouse”);}

Block类型变量

int(^blk)(int) ; blk就是Block类型变量

简化Block声明与调用

typedef int (^blk_t)(int); 则用blk_t声明就代表此block
如int类型a 为 int a; 则int (^blk_t)(int)类型的blk 为 blk_t blk;
而调用则就 int result = blk(10);

__block说明符

若无__block修饰,则Block语法中只能捕获博并保存调用瞬间时的外部自动变量值,且不支持修改。
若想要修改外部自动变量就需要添加__block修饰符,此时此自动变量可称为__block变量。
但是对于OC对象来说,调用变更该对象的方法不会发生错误。
如NSMutableArray的对象array,调用其addObject方法修改其值时不会发生错误。但若对array进行赋值操作就会发生错误。
也就是赋值会报错,但使用不会报错。
另外,Block中无法截获C语言中的数组。因此对于C语言数组而言,无论调用,还是赋值都会报错。

  • 实质篇:

Block是作为C语言源码来处理的。通过支持Block的编译器,将Block源码转换成C语言编译器能够处理的源代码,而后作为极为普通的C语言源码进行编译。

可手动通过clang的-rewrite-objc转换为C++源码。说是C++,其实只是使用了C++中的struct结构的C语言源码。

Block是OC对象,其结构体内部的isa指针放置该Block的信息的地址,形同OC对象中的元类

一个无参无反的最简单Block

void (^blk)(void) = ^{printf(“Block\n”)};
blk();

通过clang转换后会包括一大串代码,而这一大串代码等稍后总结时再看,现在先看下源码中的各个结构体:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
}

此结构体包含当前Block的实例地址与相关信息。
首先可以看到其内部有两个结构体,struct __block_impl impl 与 struct __main_block_desc_0 *Desc

__block_impl结构体的声明为

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

存储着一个isa,指向一个存储着该Block信息的地址
Flags 一个标志
Reserved 该Block升级后所存放的区域
void *FuncPtr 一个无返回值的函数指针,指向由当前Block语法指向的C语言函数指针

__main_block_desc_0结构体的声明为:

struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long 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
isa,Flags,FuncPtr都存储在struct __block_impl 中

该构造函数的调用:

void (*blk)(void) = (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 *blk = &tmp;

意思为将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型变量blk
对应于我们书写的语句: void (^blk)(void) = ^{printf(“Block\n”);};
将Block语法生成的Block赋给Block类型变量blk
加个断句,更加通俗一点的说法:将 Block语法 生成 的Block 赋给 Block类型 变量 blk
等同于将__main_block_impl_0结构体实例的指针赋给变量blk

__main_block_impl_0结构体实例构造参数:

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

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

__main_block_desc_0结构体实例的初始化部分代码

static struct __main_block_desc_0 __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

由此可知,该源代码使用Block,也就是结构体__main_block_impl_0实例的大小进行初始化

接下来再看看栈上的__main_block_impl_0 结构体实例(即Block)是如何根据这些参数初始化的,先看下该结构体:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
}

优先展开struct __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;

相应的作用之前已说过,稍后还会再说一次。这里就不赘述,先往下看吧。

接下来看一下调用此实例的部分,也就是我们书写的blk();这一行代码,转化成以下源码:

((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);

去掉转换部分:

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

简单的使用函数指针调用函数,入参为blk。
如刚才所说,由Block语法转换的__main_block_func_0 函数的指针被赋值给blk的struct __block_impl的成员变量void * FuncPtr中。
同时也说明了,__main_block_func_0函数的参数__cself指向Block值(blk)(__cself形同OC中的self,指自身。所以__cself指函数__main_block_func_0)。
到目前为止也可以看出Block(blk)是作为参数进行传递。

到此,我们来完整的总结一下关于一个无参无反,只输出一句话的Block

int main(){
    
    void (^blk)(void) = ^{
        printf("Block");
    };
    blk();
}

通过clang -rewrite-objc 转换成C语言后的源码摘取部分:

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(){

    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);
}

在main函数中,会调用

 void (*blk)(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 *blk = &tmp;

将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型变量blk
而生成__main_block_impl_0结构体实例的第一个参数__main_block_func_0,是一个是由Block语法转换的C语言函数指针,内部就是我们书写的那句输出语句 printf(“Block”);
而第二个参数&__main_block_desc_0_DATA是struct __main_block_desc_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)};

第一个参数是今后升级所需的区域,为何是0不清楚。
第二个参数是Block的大小
struct __main_block_impl_0 相当于当前block,可将此结构体中的struct __block_impl 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;

isa指针指向的地址放置该Block的信息,形同OC对象中的元类
Flags 标志
Reserved 今后版本升级所存放的区域
FuncPtr是一个函数指针,指向__main_block_func_0 也就是当前Block语法转换的C语言函数指针
Desc就是struct __main_block_desc_0 实例,存放struct今后升级所需的区域与Block的大小

而后是main函数中的第二行

  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
  去掉转换部分
  (*blk -> impl.FuncPtr)(blk);

简单的使用函数指针调用结构体中的函数,入参为blk
在函数指针FuncPtr指向的函数中调用printf(“Block”);
完成Block输出

本次探讨到此结束,预知后事如何,且听下回分解。

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

推荐阅读更多精彩内容

  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 907评论 1 3
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,744评论 5 61
  • 摘要block是2010年WWDC苹果为Objective-C提供的一个新特性,它为我们开发提供了便利,比如GCD...
    西门吹雪123阅读 903评论 0 4
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,755评论 0 23
  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C语言的扩充功能——“带有自动变量(即局部...
    SkyMing一C阅读 2,320评论 6 18