深入探究Block

Posted Xiejunyi12

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入探究Block相关的知识,希望对你有一定的参考价值。

Block的实质

感觉CSDN并不好看,去我的博客更好看些
https://junyixie.github.io/2017/01/11/Block%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/

OC中声明一个Block,并且执行

int(^example)(int) = ^(int a) 
    return a;
;
example(2);

clang -rewrite-objc main.m

C++中对应的代码

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 int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 

            return a;
        

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(int argc, const char * argv[]) 
    /* @autoreleasepool */  __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_f82934_mi_0);
        int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
    
    return 0;

static struct IMAGE_INFO  unsigned version; unsigned flag;  _OBJC_IMAGE_INFO =  0, 2 ;

挺恶心的?我看这么一大块也没胃口
代码分块 各个部分代码对应的功能

^(int a) 
    return a;
;

对应的代码

static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 
            return a;
        

通过Block使用的匿名函数实际上被作为简单的C语言函数来处理。
通过BLOCK所属的函数名和该Block语法在该函数中出现的顺序值来给经Clang变换的函数命名。__main_block_func_0

函数参数cself相当于C++实例方法中指向实例自身的变量this,或是OC中的self

cself的类型 struct __main_block_impl_0的实现代码

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

struct __main_block_impl_0 这个结构体就是examble Block的结构体,cself指向它

__block_impl的声明 Block的implementation

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

__main_block_desc_0的description

static struct __main_block_desc_0 
  //版本升级所需的保留区域
  size_t reserved;
  //Block的大小
  size_t Block_size;
 

__main_block_impl_0 block的构造函数

  __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构造函数的调用

int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

去掉类型转换的部分

int(*example)(int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));

该源码就是将__main_block_impl_0 的指针 &__main_block_impl_0 赋值给了 Block类型变量 example。

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

__block_impl 的初始化

  //构造函数
  __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

((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
//去掉转换部分
(*example->FuncPtr)(example, 2);

这就是简单的使用函数指针调用函数。

_NSConcreteStackBlock;

  impl.isa = &_NSConcreteStackBlock;

将Block指针赋值给Block结构体的成员变量isa.为了理解它,首先要理解OC类和对象的实质。其实,所谓Block就是OC对象。
OC对象
- 所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中。
- 对象有isa指针,指向类对象。
- 类对象结构体中存放着本对象的
1. 对象方法列表
2. 成员变量的列表
3. 属性列表

objc_runtime_old.h

struct objc_class : objc_object 
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;//成员变量的列表
    struct old_method_list **methodLists;//对象方法列表
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;
    ...;

struct old_class_ext 
    uint32_t size;
    const uint8_t *weak_ivar_layout;
    struct old_property_list **propertyLists;//属性列表
;

回到结构体,impl.isa 指向类对象 就是&_NSConcreteStackBlock。
_NSConcreteStackBlock相当于class_t结构体实例。在将Block作为OC的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。

截获自动变量

#import <Foundation/Foundation.h>
int b=5;
int main(int argc, const char * argv[]) 
    @autoreleasepool 
        // insert code here...
        NSLog(@"Hello, World!");
        int c = 10;
        int(^example)(int) = ^(int a) 
            a = a + b + c;
            return a;
        ;

        example(2);
    
    return 0;

用clang变换为C++源码

int b=5;

struct __main_block_impl_0 
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int c;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int flags=0) : c(_c) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 
  int c = __cself->c; // bound by copy

            a = a + b + c;
            return a;
        

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(int argc, const char * argv[]) 
    /* @autoreleasepool */  __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_664ebf_mi_0);
        int c = 10;
        int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c));

        ((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
    
    return 0;

static struct IMAGE_INFO  unsigned version; unsigned flag;  _OBJC_IMAGE_INFO =  0, 2 ;

struct __main_block_impl_0 跟上一份我们没有截获自动变量的代码区别在哪呢?


//截获自动变量int c;
struct __main_block_impl_0 
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  **int c;**
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int flags=0) : c(_c) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;

//未截获自动变量int c;
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;
  
;

可以看到Block语法表达式中的自动变量被作为成员变量添加到了__main_block_impl_0结构体中

#import <Foundation/Foundation.h>
int b=5;

有没有注意到int b = 5;虽然在Block中使用了 但没有被截获。

Block只截获在Block中使用的自动变量

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int flags=0) : c(_c) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  

构造函数中也相应的多了增加的参数

Block的值

static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 
  int c = __cself->c; // bound by copy
  a = a + b + c;
  return a;
  int c = __cself->c; // bound by copy

可以看到Block语法表达式中所使用的自动变量是被保存到Block的结构体实例中的自动变量,而不是自动变量c.

__block

自动变量在Block中是不能被改写的,这样以来就无法在Block中保存值了极其不方便。
解决这个问题有两种方法

方法一:C中允许Block改变值的变量

  • 静态变量
  • 全局变量
  • 静态全局变量

把这段代码转为C++

#import <Foundation/Foundation.h>
int b=5;
static int static_global_d = 6;
int main(int argc, const char * argv[]) 
    @autoreleasepool 
        // insert code here...
        NSLog(@"Hello, World!");
        static int static_e = 6;
        int c = 10;
        int(^example)(int) = ^(int a) 
            a = a + b + c + static_e + static_global_d;
            return a;
        ;

        example(2);
    
    return 0;

int b=5;
static int static_global_d = 6;
struct __main_block_impl_0 
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int c;
  int *static_e;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_static_e, int flags=0) : c(_c), static_e(_static_e) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 
  int c = __cself->c; // bound by copy
  int *static_e = __cself->static_e; // bound by copy

            a = a + b + c + (*static_e) + static_global_d;
            return a;
        

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(int argc, const char * argv[]) 
    /* @autoreleasepool */  __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_d6e483_mi_0);
        static int static_e = 6;
        int c = 10;
        int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &static_e));

        ((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
    
    return 0;

static struct IMAGE_INFO  unsigned version; unsigned flag;  _OBJC_IMAGE_INFO =  0, 2 ;

静态全局变量和全局变量的访问和转换前完全相同
静态局部变量则发生了变化

struct __main_block_impl_0 
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int c;
  **int *static_e;**
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_static_e, int flags=0) : c(_c), static_e(_static_e) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 
  int c = __cself->c; // bound by copy
  **int *static_e = __cself->static_e; // bound by copy**

            a = a + b + c + (*static_e) + static_global_d;
            return a;
        

可以发现,Block保存的是静态局部变量的指针!这是超出作用域使用变量的最简单方法。
实际上在由Block语法生成的Block上,可以截获自动变量的指针,但是自动变量超过作用域后,会自动被废弃,截获的指针就是野指针了

方法二:__block

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) 
    @autoreleasepool 
        // insert code here...
        NSLog(@"Hello, World!");
        __block int c = 10;
        int(^example)(int) = ^(int a) 
            c = 11;
            a = a + c;
            return a;
        ;

        example(2);
    
    return 0;

struct __Block_byref_c_0 
  void *__isa;
__Block_byref_c_0 *__forwarding;
 int __flags;
 int __size;
 int c;
;

struct __main_block_impl_0 
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_c_0 *c; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_c_0 *_c, int flags=0) : c(_c->__forwarding) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 
  __Block_byref_c_0 *c = __cself->c; // bound by ref

            (c->__forwarding->c) = 11;
            a = a + (c->__forwarding->c);
            return a;
        
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) _Block_object_assign((void*)&dst->c, (void*)src->c, 8/*BLOCK_FIELD_IS_BYREF*/);

static void __main_block_dispose_0(struct __main_block_impl_0*src) _Block_object_dispose((void*)src->c, 8/*BLOCK_FIELD_IS_BYREF*/);

static struct __main_block_desc_0 
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
 __main_block_desc_0_DATA =  0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0;
int main(int argc, const char * argv[]) 
    /* @autoreleasepool */  __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_9f6ec2_mi_0);
        __attribute__((__blocks__(byref))) __Block_byref_c_0 c = (void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 10;
        int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_c_0 *)&c, 570425344));

        ((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
    
    return 0;

static struct IMAGE_INFO  unsigned version; unsigned flag;  _OBJC_IMAGE_INFO =  0, 2 ;

使用__block修饰的自动变量 转换为c++代码后发生了如下变化

__attribute__((__blocks__(byref))) __Block_byref_c_0 c = (void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 10;

通过__block修饰后,自动变量竟然变成了结构体实例。

struct __Block_byref_c_0 
  void *__isa;
__Block_byref_c_0 *__forwarding;
 int __flags;
 int __size;
 int c;
;

通过这段代码 int c; 也出现在结构体中,该结构体持有相当于原自动变量的成员变量。

使用Block的代码

static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) 
  __Block_byref_c_0 *c = __cself->c; // bound by ref
            (c->__forwarding->c) = 11;
            a = a + (c->__forwarding->c);
            return a;

这段代码的意思基本上如图所示

__forwarding 将在 Block的存储域 中提到.

https://junyixie.github.io/

以上是关于深入探究Block的主要内容,如果未能解决你的问题,请参考以下文章

深入探究docker attach的退出方式

Block实现-block的实质

Block探究

深入探究js中的隐式变量声明

iOS底层原理 - Block本质探究

block探究一