当前位置:   article > 正文

【iOS】——Block底层实现和捕获机制

【iOS】——Block底层实现和捕获机制

Block的实质

Block的定义是带有自动变量的匿名函数,下面从源码的角度探究下Block究竟是什么

下面是一个Block的简单实现:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void (^blk)(void) = ^{
            printf("Block\n");
        };
        blk();
    }
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

通过clang编译器转换为如下的C++代码:

// 定义Block的实现结构体
struct __block_impl {
  void *isa;      // 指向所属类的指针
  int Flags;      // 标志性参数,暂时没用到所以默认为0
  int Reserved;   // 今后版本升级所需的区域大小。
  void *FuncPtr;  // 函数指针,指向实际执行的函数,也就是Block中花括号里面的代码内容。
};

// 定义具体的Block结构体
struct __main_block_impl_0 {
  struct __block_impl impl; // 上面定义的Block实现结构体的变量
  struct __main_block_desc_0* Desc; // Block描述符的指针
  
  // 构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {
    impl.isa = &_NSConcreteStackBlock; // 设置Block的类型标识
    impl.Flags = flags;                // 设置标志
    impl.FuncPtr = fp;                 // 设置Block的执行函数指针
    Desc = desc;                       // 设置Block的描述符
  }
};

// Block的实际执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  printf("Block\n"); // Block执行时输出的内容
}

// Block描述符结构体
static struct __main_block_desc_0 {
  size_t reserved;  // 今后版本升级所需区域的大小(一般填0)
  size_t Block_size;   // Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; // 初始化Block描述符

// 主函数
int main(int argc, const char * argv[]) {
  // 创建Block
  void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  
  // 调用Block
  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
  
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

可以看到^{printf("Block\n");转换成了

// Block的实际执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  printf("Block\n"); // Block执行时输出的内容
}
  • 1
  • 2
  • 3
  • 4

block的匿名函数被转换成普通C语言函数,函数命名按照block语法所属的函数名(此处为main函数)和在该函数中出现的顺序(此处为0)。

这里的_ self相当于OC中的指向对象自身的self,也就是_ self指向block值的变量

_ self参数是__main_block_impl_0的参数

__main_block_impl_0

// 定义具体的Block结构体
struct __main_block_impl_0 {
  struct __block_impl impl; // 上面定义的Block实现结构体的变量
  struct __main_block_desc_0* Desc; // Block描述符的指针
  
  // 构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {
    impl.isa = &_NSConcreteStackBlock; // 设置Block的类型标识
    impl.Flags = flags;                // 设置标志
    impl.FuncPtr = fp;                 // 设置Block的执行函数指针
    Desc = desc;                       // 设置Block的描述符
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里主要定义类两个结构体,第一个成员是__block_impl 结构体,第二个是__main_block_desc_0,和实现构造函数

__block_impl

struct __block_impl {
  void *isa;//指向所属类的指针
  int Flags;//标志性参数,暂时没用到所以默认为0
  int Reserved;//今后版本升级所需的区域大小。
  void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果block变量为空的话,这里的FuncPtr指针就会指向错误的地址而不是实际执行的函数,此时就会报错。

__main_block_desc_0

static struct __main_block_desc_0 {
  size_t reserved;  //今后版本升级所需区域的大小(一般填0)
  size_t Block_size;   //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

  • 1
  • 2
  • 3
  • 4
  • 5

第一个成员变量指的是今后版本升级所需区域的大小(一般填0)。
第二个成员变量是Block的大小。

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

  • 这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA。

  • 其中reserved为0,Block_size是sizeof(struct __main_block_impl_0)。

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这个结构体的构造函数里,isa指针保持这所属类的结构体的实例的指针。_mainblockimlp0结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现

main函数

// 主函数
int main(int argc, const char * argv[]) {
  // 创建Block
  // 使用类型转换将Block结构体的地址转换成函数指针类型
  void (*blk)(void) = 
    // 将Block结构体转换为函数指针类型
    ((void (*)())&__main_block_impl_0(
      // Block的执行函数指针
      (void *)__main_block_func_0, 
      // Block描述符的地址
      &__main_block_desc_0_DATA
    ));

  // 调用Block
  // 获取Block结构体中的FuncPtr成员,并进行必要的类型转换后调用
  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

第一部分是将__main_block_impl_0结构体类型的自动变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。

第二部分是相当于源代码中的blk(),即使用该Block部分。去掉转换部分就是:

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

  • 1
  • 2

使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。

Block捕获机制

block变量捕获就是在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。

如果Block外面还有很多自动变量,静态变,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

Untitled 1.png

下面是对于不同变量时Block的捕获:

  • 全局变量: 不捕获
  • 局部变量: 捕获值
  • 静态全局变量: 不捕获
  • 静态局部变量: 捕获指针
  • const修饰的局部常量:捕获值
  • const修饰的静态局部常量:捕获指针

当Block捕获的是变量的值时,在block内部不能修改变量,当Block捕获的是变量的指针时,在block内部可以修改变量

对于全局变量Block是进行直接访问的

全局变量:

int max = 3;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void (^blk)(void) = ^{
            printf("%d\n", max);
        };
        max = 60;
        blk();
    }
    
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

局部变量:

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述

静态全局变量

static int max = 3;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void (^blk)(void) = ^{
            printf("%d\n", max);
        };
        max = 60;
        blk();
    }
    
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

静态局部变量

static int val = 10;
        const char *fmt = "val = %d\n";
        void (^blk)(void) = ^{
            val = 2;
            
            printf(fmt, val);
        };
        fmt = "These values were changed. val = %d\n";
        blk();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述

Block捕获普通变量

int main(int argc, const char * argv[]) {
	int dmy = 256;
    int val = 10;
    const char  *fmt = "val = %d\n";
    void (^blk)(void) = ^{
    	printf(fmt, val);
    };
    blk();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

转化后的代码:

// Block结构体定义
struct __block_impl {
  void *isa;        // 指向Block类型的标识
  int Flags;        // Block标志位
  int Reserved;     // 保留字段
  void *FuncPtr;    // 指向Block执行函数的指针
};

// 定义特定于main函数的Block实现
struct __main_block_impl_0 {
  struct __block_impl impl; // 包含Block的基本实现
  struct __main_block_desc_0* Desc; // 指向Block描述符
  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; // 设置Block类型标识
    impl.Flags = flags;               // 设置Block标志位
    impl.FuncPtr = fp;                // 设置Block执行函数的指针
    Desc = desc;                      // 设置Block描述符
  }
};

// Block执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt;  // 获取格式字符串
  int val = __cself->val;          // 获取值

  printf(fmt, val);                // 执行打印操作
}

// Block描述符
static struct __main_block_desc_0 {
  size_t reserved;                 // 保留字段
  size_t Block_size;               // Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };

// 主函数
int main(int argc, const char * argv[]) {
  int dmy = 256;                   // 未使用的变量
  int val = 10;                    // 用于输出的整数值
  const char *fmt = "val = %d\n";  // 输出格式字符串

  // 创建Block
  void (*blk)(void) = 
    // 类型转换为函数指针
    ((void (*)())&__main_block_impl_0(
      // Block执行函数指针
      (void *)__main_block_func_0, 
      // Block描述符地址
      &__main_block_desc_0_DATA, 
      // 格式字符串
      fmt, 
      // 用于输出的整数值
      val
    ));

  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

与上次不同的是在block内部语法表达式中使用的自动变量(fmt,val)被作为成员变量追加到了_mainblockimpl0结构体中(注意:block没有使用的自动变量不会被追加,如dmy变量)。

struct __main_block_impl_0 {
  struct __block_impl impl; // 包含Block的基本实现
  struct __main_block_desc_0* Desc; // 指向Block描述符
  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; // 设置Block类型标识
    impl.Flags = flags;               // 设置Block标志位
    impl.FuncPtr = fp;                // 设置Block执行函数的指针
    Desc = desc;                      // 设置Block描述符
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在Block执行函数中,fmt,var都是从__cself里面获取的,更说明了二者是属于block的.

这两个变量是值传递,而不是指针传递,也就是说Block仅仅截获自动变量的值,所以这就解释了即使改变了外部的自动变量的值,也不会影响Block内部的值。

// Block执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt;  // 获取格式字符串
  int val = __cself->val;          // 获取值

  printf(fmt, val);                // 执行打印操作
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Block捕获_ _block修饰的变量

编译器会将_ _block修饰的变量包装成一个对象,变成对象后就可以根据指针地址在block内部去修改外部的变量,block通过**__forwarding**指针去修改变量的值

int main(int argc, const char * argv[]) {
    
    __block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    };
    
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

转换后的代码:

//__block说明符修饰后的变量的结构体
struct __Block_byref_val_0 {
	void *__isa;  //指向所属类的指针
	__Block_byref_val_0 *__forwarding;  //指向自己的内存地址的指针
	int __flags;  //标志性参数,暂时没用到所以默认为0
	int __size;  //该结构体所占用的大小
	int val;  //该结构体存储的值,即原变量的赋的值
};

//block本体
struct __main_block_impl_0 {
	struct __block_impl impl;  //block的主体
	struct __main block desc 0* Desc;  //存储该block的大小
	__Block_byref_val_0 *val;  //__block修饰的变量的值
	//构造函数
	__main_block_impl_0(void *fp, struct __main_block_desc 0 *desc, __Block_byrefval_0 *_val, int flags=0) : val(_val->__forwarding) {
	impl.isa = &_NSConcreteStackBlock;
	impl.Flags = flags;
	impl.FuncPtr = fp;
	Desc = desc;
};

//封装的block逻辑,存储了block的代码块
static void __main_block_func_0(struct__main_block_impl_0 *_cself) {
	__Block_byref_val_0 *val =__cself->val;
	
	(val->__forwarding->val) = 1;
}
static void_main_block_copy_0(struct __main_block_impl_0* dst, struct __main_block_impl_0* src) {
    //根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
	_Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}

static void __main_block_dispose_0(struct __main_block_imp1_0* src) {
    //自动释放引用的auto变量(相当于release)
	_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}

static struct __main_block_desc_0 {
	unsigned long reserved;  //保留字段
	unsigned long Block_size;  //block大小
	void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);  //copy的函数指针,下面使用构造函数进行了初始化
	void (*dispose)(struct __main_block_impl_0*);  //dispose的函数指针,下面使用构造函数进行了初始化
}
    //构造函数,初始化保留字段、block大小及两个函数
    __main_block_desc_0_DATA = {
	0,
	sizeof(structmain_block_impl_0),
	__main_block_copy_O, 
	__main_block_dispose_0
};
int main() {
    //之前的 __block int val = 10;变成了结构体实例
	struct __Block_byref_val_0 val = {
		0,  //isa指针
		&val,  //指向自身地址的指针
		0,  //标志变量
		sizeof(__Block_byref_val_0),  //block大小
		10  //该数据的值
	};
	blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);

	return 0;
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

通过源代码发现__block说明符修饰的变量的结构体__多了个指针_ _Block_byref_val_0 *__forwarding,指向自己的内存地址的指针

struct __Block_byref_val_0 {
	void *__isa;  //指向所属类的指针
	__Block_byref_val_0 *__forwarding;  //指向自己的内存地址的指针
	int __flags;  //标志性参数,暂时没用到所以默认为0
	int __size;  //该结构体所占用的大小
	int val;  //该结构体存储的值,即原变量的赋的值
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

__main_block_func_0打印的并不是_ _Block_byref_val_0* val而是(val->__forwarding->val)并且 _ _block说明符修饰的变量变成了一个结构体

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; 
  (val->__forwarding->val) = 1;
  printf("val = %d\n", (val->__forwarding->val));
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

那么为什么会有成员变量__forwarding呢?__

因为__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确的访问__block变量。

在这里插入图片描述

总结

  • Block的本质是一个对象,其内部的第一个成员是isa指针,通过内部的FuncPtr指针指向实际执行的函数,也就是Block中花括号里面的代码内容。
  • Block只会捕获自己需要使用的自动变量并将其保存进了Block的结构体实例中,也就是Block自身中。
  • Block捕获普通的局部变量只是通过构造函数将它的值传递进了Block的结构体实例中的成员,因此不能在Block内部修改值,并且在Block外修改的值也不会影响Block捕获时的值
  • Block捕获static修饰的局部变量是捕获它的指针,因此可以在Block内部修改值
  • Block捕获_ _Block变量时会将其转换成结构体并且生成_ _Block_byref_val_0 *__forwarding指针,指向自己的内存地址的指针,通过该指针就能访问和修改 _ _Blcok变量
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/900679
推荐阅读
相关标签
  

闽ICP备14008679号