Block实现原理

主要介绍block的类型和底层分析

block类型

block主要由三种类型

  • NSGlobalBlock:全局block,存储在全局区
void(^block)(void) = ^{
    NSLog(@"LTD");
};
NSLog(@"%@", block);

当前block内没有捕获外部变量,属于全局block。

截屏2021-07-16 上午11.49.58.png
  • NSMallocBlock:堆区block,因为block既是函数,也是对象
        int a = 10;
        void(^testBlock)(void) = ^{
            NSLog(@"%d",a);
        };
        testBlock();
        NSLog(@"%@",testBlock);

当前block会捕获外界变量,是堆区block。


截屏2021-07-16 下午1.15.42.png
  • NSStackBlock:栈区block
        int a = 10;
        void(^__weak testBlock)(void) = ^{
            NSLog(@"%d",a);
        };
        NSLog(@"%@",testBlock);

如果不用__weak修饰变量,那么该变量就是NSMallocBlock。但是通过__weak修饰变量后,那该变量类型就是栈区block

截屏2021-07-16 下午1.30.15.png

总结:

  • block没有捕获变量到block内,那block直接存储在全局区
  • 如果block捕获变量
    • 此时block被强引用,则block存储在堆区,即堆区block
    • 此时block被弱引用(通过__weak修饰),则block存储在栈区,即栈区block
截屏2021-07-12 上午9.55.02.png
截屏2021-07-12 上午9.55.15.png

block底层分析

通过clang分析不同类型的block底层数据结构,怎么捕获外部变量。

数据结构

创建一个block

   int a = 10;
   void(^testBlock)(void) = ^{
      NSLog(@"%d",a);
   };
   NSLog(@"%@",testBlock);

通过 clang -rewrite-objc main.m -o mian.cpp 生成main.cpp文件,查看编译后的数据格式

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_1,testBlock);

    }
    return 0;
}

去除类型转换的括号,简化为

 void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);

block的数据类型为__main_block_impl_0,是一个结构体,同时可以说明block是一个__main_block_impl_0类型的对象,这也是为什么block能够%@打印的原因。

//block结构体类型
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
//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)};

//block方法的结构体类型
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_0,a);
        }

//block代码块的结构体类型
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

总结:
block的本质是一个结构体,其内部包含block基本的数据结构代码块的结构体方法访问的外部变量。因为block数据结构内部有isa指针,所以block本质也是对象。由于block函数没有名称,也被称为匿名函数

1、block为什么需要调用

block底层的类型为__main_block_impl_0结构体,通过其同名构造函数创建,第一个传入的block的内部实现代码块,即__main_block_func_0,用fp表示,然后赋值给implFuncPtr属性,然后在main中进行了调用。如果不调用,block内部实现的代码块将无法执行,可以总结为以下两点

  • 函数声明:即block内部实现声明成了一个函数__main_block_func_0
  • 执行具体的函数实现:通过调用blockFuncPtr指针,调用block的代码块执行

2、block是如何获取外界变量的

block捕获外部值类型

捕获外部int是不能进行修改

   int a = 10;
   void(^testBlock)(void) = ^{
      NSLog(@"%d",a);
   };
   NSLog(@"%@",testBlock);

编译后代码分析

int a = 10;
// 初始化block结构体 传入a
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        
//去除类型转换简化后 void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);


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

__main_block_impl_0初始化时,使用初始化列表来初始化字段设置结构体捕获的变量a值为10。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy 值拷贝,即 a = 10,此时的a与传入的__cself的a并不是同一个

        printf("CJL - %d", a);
}

执行block代码块,其内部重新初始变量a赋值。
总结:
block捕获外界变量时,在内部会自动生成同一个属性来保存,代码块内部会copy该属性执行。

block捕获外部对象

初始化一个数组,block捕获修改数组成功


截屏2021-07-16 下午3.56.05.png

编译后代码分析

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSMutableArray *array = __cself->array; // bound by copy

            ((void (*)(id, SEL, ObjectType _Nonnull, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("setObject:atIndexedSubscript:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), (NSUInteger)0);
        }

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSMutableArray * array = ((NSMutableArray *(*)(id, SEL, ObjectType _Nonnull, ...))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("initWithObjects:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 8), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 9), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 10), __null);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_0,array);
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_1,array);

    }
    return 0;
}
  • __main_block_impl_0初始化是传入数组,赋值给属性array
  • __main_block_func_0代码块内操作捕获数组进行修改。

总结:

  • 捕获的外界对象(强引用),操作原对象。

__block的原理

变量a进行__block修饰,然后在block中对a进行++操作

        __block int a = 10;
        void(^testBlock)(void) = ^{
            a++;
            NSLog(@"%d",a);
        };
        testBlock();
        NSLog(@"%@",testBlock);

通过底层编译如下

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a)++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_0,(a->__forwarding->a));
        }

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_1,testBlock);

    }
    return 0;
}
  • 在main函数内部先初始化一个__Block_byref_a_0结构体,__Block_byref_a_0初始时,将&a强转类型为__Block_byref_a_0结构体,再赋值到__forwarding
  • __main_block_impl_0初始化时传入__Block_byref_a_0(&a),赋值给__Block_byref_a_0 *a属性(强引用)
  • __main_block_func_0代码块内,通过__cself->a读取捕获的参数对象,再读取属性结构体内__forwarding(&a),取值进行处理。实则是操作捕获同一个内存空间

总结:

  • 外界变量会生成__Block_byref_a_0结构体
  • 结构体用来保存原始变量的指针和值
  • 将变量生成的结构体对象的指针地址传递给代码块,代码块内操作的就是原变量地址。

你可能感兴趣的:(Block实现原理)