Block 知识点总结

面试中经常会被问到什么是 Block, 谈谈你对 Block 的理解, 今天就做一个小的总结。

Block 定义

block 是将函数及其上下文封装起来的对象

首先, 定义一个简单的 AddBlock

  - (void)test1 {
    
    void(^AddBlock)(NSInteger a , NSInteger b) = ^(NSInteger a, NSInteger b) {
        NSLog(@"%ld",a+b);
    };
    AddBlock(3,4);
}

接着运行相关命令转换成 C++ 文件

clang -rewrite-objc TestBlock.m

__block_impl

struct __block_impl {
  void *isa;  // isa指针, 验证了 block 也是一个对象
  int Flags;
  int Reserved;
  void *FuncPtr; // 执行调用的函数指针
};

具体调用流程

  struct __TestBlock__test1_block_impl_0 {
  struct __block_impl impl;
  struct __TestBlock__test1_block_desc_0* Desc;
  __TestBlock__test1_block_impl_0(void *fp, struct __TestBlock__test1_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __TestBlock__test1_block_func_0(struct __TestBlock__test1_block_impl_0 *__cself, NSInteger a, NSInteger b) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_1v6qx94n2gl27sx0j1dqlrs40000gn_T_TestBlock_54a84e_mi_0,a+b);
    }

static struct __TestBlock__test1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __TestBlock__test1_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__test1_block_impl_0)};

static void _I_TestBlock_test1(TestBlock * self, SEL _cmd) {

    void(*AddBlock)(NSInteger a , NSInteger b) = ((void (*)(NSInteger, NSInteger))&__TestBlock__test1_block_impl_0((void *)__TestBlock__test1_block_func_0, &__TestBlock__test1_block_desc_0_DATA));
    ((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)AddBlock)->FuncPtr)((__block_impl *)AddBlock, 3, 4);
}

最终会执行 __TestBlock__test1_block_impl_0 方法, 并把 isa 指针指向了 _NSConcreteStackBlock, desc 包含了 block 结构体大小等信息

截获变量

定义基本类型和对象相关的全局变量、静态全局变量、局部变量,局部静态变量

#import "TestBlock.h"


static int static_global_a; // 基本类型静态全局变量
int global_a; // 基本类型全局变量

static NSObject *static_global_object; // 静态全局对象
NSObject *global_object;    // 全局对象

@implementation TestBlock


- (void)test2 {
    static int static_inside_a; // 基本类型局部静态变量
    int inside_a; // 基本类型局部变量
    static NSObject *static_inside_object;// 静态局部对象
    NSObject *inside_object; // 局部对象
    
    static_global_a = 1;
    global_a = 2;
    static_inside_a = 3;
    inside_a = 4;
    
    static_global_object = [[NSObject alloc] init];
    global_object = [[NSObject alloc] init];
    static_inside_object = [[NSObject alloc] init];
    inside_object = [[NSObject alloc] init];
    
    void(^Block)(void) = ^(){
        NSLog(@"截获基本数据类型 %d,%d,%d,%d",static_global_a,global_a ,static_inside_a,inside_a);
        
        NSLog(@"截获对象 %@,%@,%@,%@",static_global_object,global_object ,static_inside_object,inside_object);
    };
    
    Block();
}
@end

转换后的实现如下

struct __TestBlock__test2_block_impl_0 {
  struct __block_impl impl;
  struct __TestBlock__test2_block_desc_0* Desc;
  int *static_inside_a;
  int inside_a;
  NSObject **static_inside_object;
  NSObject *inside_object;
  __TestBlock__test2_block_impl_0(void *fp, struct __TestBlock__test2_block_desc_0 *desc, 
                                  int *_static_inside_a, int _inside_a, 
                                  NSObject **_static_inside_object, NSObject *_inside_object, int flags=0) : static_inside_a(_static_inside_a), inside_a(_inside_a), static_inside_object(_static_inside_object), inside_object(_inside_object) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以发现 Block 只截获了 基本类型和对象的局部变量, 不同点是静态变量是以指针形式存在的, 并没有截获全局变量、静态全局变量, 对象类型的局部变量连同修饰权一起截获

类型 局部 静态局部 全局 静态全局
基本类型 ✔️ ✔️ ✖️ ✖️
对象 ✔️ ✔️ ✖️ ✖️

__block 修饰符

用简单的话来说, 如果要对截获的变量进行赋值操作, 需要添加 __block 修饰符

截获变量.png

用下面的例子看一下 __block 的具体实现

  - (void)test3 {
    __block int b = 2;
    void(^Block)(void) = ^(){
        b = 4;                  // __block 修饰后可以直接赋值
        NSLog(@"%d",b);
    };
    Block();
}

__block 修饰的对象转变成结构体 __Block_byref_b_0

  struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

struct __TestBlock__test3_block_impl_0 {
  struct __block_impl impl;
  struct __TestBlock__test3_block_desc_0* Desc;
  __Block_byref_b_0 *b; // by ref
  __TestBlock__test3_block_impl_0(void *fp, struct __TestBlock__test3_block_desc_0 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __TestBlock__test3_block_func_0(struct __TestBlock__test3_block_impl_0 *__cself) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref

        (b->__forwarding->b) = 4;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_1v6qx94n2gl27sx0j1dqlrs40000gn_T_TestBlock_b681a6_mi_2,(b->__forwarding->b));
    }

可以看到 __block 修饰的对象 b 重新赋值后, 底层转变为 b->__forwarding->b

__forwarding 作用

wa

__forwarding 存在的意义就是不论在任何位置, 都能够顺利访问同一个 __block 变量

Block 类型

  • _NSConcreteStackBlock
  • _NSConcreateGlobalBlock
  • _NSConcreateMallocBlock

MRC 情况下:

  • _NSConcreateGlobalBlock:未截获外部变量的情况
  • _NSConcreteStackBlock:引用外部变量,
  • _NSConcreateMallocBlock : [block copy] 或者截获__block 修饰的变量

ARC 情况下

  • _NSConcreateGlobalBlock : 未截获外部变量的情况
  • _NSConcreateMallocBlock : 变量在赋值的时候自动拷贝到堆区

内存管理

Block 内存管理.png

MRC 下, 栈上的 Block 随着作用域结束会被销毁, 堆上的 Block 随着作用域结束不会被释放

你可能感兴趣的:(Block 知识点总结)