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

96
DrunkenMouse
0.1 2016.11.10 17:15* 字数 1942

章节目录

  • 关于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输出

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

日记本
Web note ad 1