Block底层实现原理

45次阅读

共计 4844 个字符,预计需要花费 13 分钟才能阅读完成。

古巷悠悠岁月深,青石老街印旧痕
今夜小楼听风雨,不见当年伞下人

前言

Block 作为 iOS 中老生常谈的问题,也是面试中面试官比较喜欢问的 一个问题,下面我们通过源码查看 block 的底层实现原理

什么是 Block

Block:将 函数 及其 上下文 组装起来的 对象

Block 本质就是一个对象

创建一个 PHJBlock 类

    @implementation PHJBlock
    
    - (void)test {
        int a = 10;
        void (^ block)(void) = ^{NSLog(@"%d", a);
        };
        block();}
    
    @end

查看编译后的 C ++ 源码

编译:Clang -rewrite-objc PHJBlock.m

    static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
        int a = 10;
        void (* block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }   
    

__PHJBlock__test_block_impl_0 的内部实现

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

__block_impl 的内部实现

看到 isa,证明 block 的本质就是一个对象

    struct __block_impl {
      void *isa; // 看到 isa,证明 block 的本质就是一个对象
      int Flags;
      int Reserved;
      void *FuncPtr;
    };

Block 的三种类型

栈 Block
堆 Block
全局 Block

        int a = 10;
        // 堆 block
        void(^block)(void) = ^{NSLog(@"%d", a);
        };
        block();
        NSLog(@"%@", block);
        
        // 全局 block
        void(^block1)(void) = ^{ };
        block1();
        NSLog(@"%@", block1);
        
        // 栈 block
        NSLog(@"%@", ^{NSLog(@"%d", a);
        });

打印查看 block 内存地址

Block 捕获外部变量

局部变量

基本数据类型:截获其值
对象类型:对于对象类型的局部变量连同所有权修饰符一起截获

静态局部变量

以指针形式

全局变量

不截获

静态全局变量

不截获

Block 捕获基本数据类型局部变量

不加修饰词修饰的变量

    - (void)test {
        int a = 100;
        void(^block)(void) = ^{printf("%d", a);
        };
        block();}

编译后的 C ++ 代码:捕获外部变量的值

    static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {
      // 捕获 a 的值
      int a = __cself->a; // bound by copy
            printf("%d", a);
        }
    

加修饰词修饰的变量

    - (void)test {
        __block int a = 100;
        void(^block)(void) = ^{
            a ++;
            printf("%d", a);
        };
        block();}

编译后的 C ++ 代码:捕获外部变量的的地址

总结:这也就解释了为什么我们在外部变量前加上__block 就能在 block 内部可以修改变量的值

    static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {
      // 捕获 a 的地址
      __Block_byref_a_0 *a = __cself->a; // bound by ref
    
            (a->__forwarding->a) ++;
            printf("%d", (a->__forwarding->a));
    }

发现变量 a 加上__block 后变成了一个对象
__forwarding 指针

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

Block 捕获对象类型局部变量

    - (void)test {
        
        __unsafe_unretained id obj = nil;
        __strong NSObject *obj1 = nil;
        
        void(^block)(void) = ^{NSLog(@"__unsafe_unretained 类型变量:%@", obj);
            
            NSLog(@"__strong 类型变量:%@", obj1);
        };
        block();}

编译后的 C ++ 代码:连同外部变量的修饰词一起捕获

    static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
    
        // 修饰词一起捕获
        __attribute__((objc_ownership(none))) id obj = __null;
        __attribute__((objc_ownership(strong))) NSObject *obj1 = __null;
    
        void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, obj, obj1, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }

总结:这也就解释了为什么我们在外部对象前加上__weak 就能在 block 内部使用的时候可以避免循环引用问题

Block 捕获静态局部变量

    - (void)test {
        static int a = 100;
        void(^block)(void) = ^{NSLog(@"static 类型变量 a:%d", a);
        };
        block();}

编译后的 C ++ 代码:捕获变量的地址

&a代表传入的是 a 变量的地址

    static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
        static int a = 100;
        // 下面的 &a 代表传入的是 a 变量的地址
        void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, &a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }

int *a接收传入的 a 变量的地址

    struct __PHJBlock__test_block_impl_0 {
      struct __block_impl impl;
      struct __PHJBlock__test_block_desc_0* Desc;
      // 捕获 a 变量的地址
      int *a;
      __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

Block 不会捕获全局变量

    int a = 100;
    
    @implementation PHJBlock
    
    - (void)test {void(^block)(void) = ^{NSLog(@"全局变量 a:%d", a);
        };
        block();}
    
    @end

编译后的 C ++ 代码:不会捕获全局变量

    struct __PHJBlock__test_block_impl_0 {
      struct __block_impl impl;
      struct __PHJBlock__test_block_desc_0* Desc;
      __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

Block 不会捕获静态全局变量

    static int a = 100;
    
    @implementation PHJBlock
    
    - (void)test {void(^block)(void) = ^{NSLog(@"全局变量 a:%d", a);
        };
        block();}
    
    @end

编译后的 C ++ 代码:不会捕获静态全局变量

    struct __PHJBlock__test_block_impl_0 {
      struct __block_impl impl;
      struct __PHJBlock__test_block_desc_0* Desc;
      __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

一般情况下对截获变量进行赋值操作需要加__block

需要加__block

    __block NSMutableDictionary *dicM = nil;
    void(^block)(void) = ^{dicM = [NSMutableDictionary dictionary];
    };
    block();

不需要加__block

    NSMutableDictionary *dicM = nil;
    void(^block)(void) = ^{[dicM setObject:@(1) forKey:@"1"];
    };
    block();

总结:赋值的时候需要加__block,操作使用的时候不用加

__forwarding 指针

栈 Block 没有进行 copy 操作

 栈__forwarding 指针都指向栈中自己的变量

栈 Block 如果进行了 copy 操作

栈和堆上的__forwarding 指针都指向堆的变量

总结:不论在任何内存位置,都可以顺利访问同一个__block 变量

Block 的 Copy 操作

栈 block 进行 copy, 得到堆 block
堆 block 进行 copy, 增加引用计数
全局 block 进行 copy,block 不会产生影响

__block 修饰局部对象类型变量的循环引用问题

MRC 下, 不会产生循环引用
在 ARC 下, 会产生循环引用,引起内存泄漏,解决:在 block 内部对__block 修饰的对象变量进行置 nil 操作

正文完
 0