美文网首页
[iOS] block分析

[iOS] block分析

作者: 沉江小鱼 | 来源:发表于2021-01-25 22:20 被阅读0次

    1. block 的类型

    我们通过下面的代码,来分析一下block 的类型:

    static int n = 99;
    static NSString *name = @"test";
    
    - (void)testBlockType{
        // 0.没有引用外部变量的 block0
        void(^block0)(void) = ^{
            NSLog(@"block0");
        };
        NSLog(@"block0 => %@",block0);
        
        // 1.引用静态变量的 block1
        void(^block1)(void) = ^{
            NSLog(@"%@",name);
            NSLog(@"block1");
        };
        NSLog(@"block1 => %@",block1);
        
        // 2.引用局部变量的 block2
        int a = 1;
        void(^block2)(void) = ^{
            NSLog(@"%d",a);
            NSLog(@"block2");
        };
        NSLog(@"block2 => %@",block2);
        
        // 3.没有引用外部变量且弱引用修饰的 block3
        void(^__weak block3)(void) = ^{
            NSLog(@"block3");
        };
        NSLog(@"block3 => %@",block3);
        
        // 4.引用静态变量且弱引用修饰的 block4
        void(^__weak block4)(void) = ^{
            NSLog(@"%@",name);
            NSLog(@"block4");
        };
        NSLog(@"block4 => %@",block4);
        
        // 5.引用局部变量且弱引用修饰的 block5
        void(^__weak block5)(void) = ^{
            NSLog(@"%@",a);
            NSLog(@"block5");
        };
        NSLog(@"block5 => %@",block5);
    }
    

    输出结果:

    2021-01-24 21:47:57.223205+0800 block[10936:934202] block0 => <__NSGlobalBlock__: 0x107102048>
    2021-01-24 21:47:57.223361+0800 block[10936:934202] block1 => <__NSGlobalBlock__: 0x107102068>
    2021-01-24 21:47:57.223444+0800 block[10936:934202] block2 => <__NSMallocBlock__: 0x600000d25200>
    2021-01-24 21:47:57.223518+0800 block[10936:934202] block3 => <__NSGlobalBlock__: 0x1071020a8>
    2021-01-24 21:47:57.223605+0800 block[10936:934202] block4 => <__NSGlobalBlock__: 0x1071020c8>
    2021-01-24 21:47:57.223719+0800 block[10936:934202] block5 => <__NSStackBlock__: 0x7ffee8afe0f8>
    

    可以看到block 有 3 中类型:NSGlobalBlockNSMallocBlockNSStackBlock

    1. 没有引用任何外部变量,或者引用了静态变量block 都为NSGlobalBlock
    2. 引用了局部变量且没有__weak修饰的blockNSMallocBlock(堆 block)
    3. 引用了局部变量__weak修饰的block,为NSStackBlock(栈 block)

    2. block 的循环引用

    我们来看下面这两段代码中是否会产生循环引用:

    //代码一
    NSString *name = @"CJL";
    self.block = ^(void){
        NSLog(@"%@",self.name);
    };
    self.block();
    
    //代码二
    UIView animateWithDuration:1 animations:^{
        NSLog(@"%@",self.name);
    };
    

    结果:
    代码一发生了循环引用,因为在block 内部使用了外部变量 name,导致 block 持有 self,而 block 又是 self 的属性,所以导致了 self 和 block 相互持有
    代码二中没有循环引用,虽然也使用了外部变量,但是self 中并没有持有 animation 的 block,没有相互引用。

    解决循环引用常见的方式有以下几种:

    • weak-strong
    • __block修饰对象(需要注意的是在block 内部需要置空对象,且 block 必须调用)
    • 传递对象self作为 block的参数,提供给block 内部使用
    • 使用NSProxy
    2.1 weak-strong

    __weak 修饰 self 即可,这也是我们日常开发中使用最多的:

    typedef void(^CJLBlock)(void);
    
    @property(nonatomic, copy) CJLBlock cjlBlock;
    
    __weak typeof(self) weakSelf = self;
    self.cjlBlock = ^(void){
         NSLog(@"%@",weakSelf.name);
    }
    
    2.2 __block修饰变量

    通过__block修饰对象,主要是因为__block修饰的对象是可以改变的:

    __block ViewController *vc = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;//手动释放
        });
    };
    // 这里必须调用 block,否则 block 不执行,其内部持有的 vc 不会被释放
    self.block();
    

    需要注意的是这里的block必须调用,如果不调用blockvc就不会置空,那么依旧是循环引用selfblock都不会被释放。

    2.3 对象 self 作为参数

    主要是将对象self作为参数,提供给block内部使用,不会有引用计数问题:

    typedef void(^TestBlock)(ViewController *);
    
    @property(nonatomic, copy) TestBlock block;
    
    self. block = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    self.block(self);
    
    2.4 使用NSProxy虚拟类

    具体使用可以看这篇文章:NSTimer循环引用的解决办法

    3. block 底层分析

    定义一个block.c文件:

    #include <stdio.h>
    
    int main(){
        void(^testBlock)(void) = ^{
            printf("123456");
        };
        testBlock();
        return 0;
    }
    
    

    通过下面的命令,将 block.c编译成 block.cpp文件:

    xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
    

    block在底层被编译成了下面的形式:

    // block代码块的结构体类型
    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 的内部实现
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("123456");
    }
    
    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(){
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
        return 0;
    }
    
    // ********main()函数的简化*********
    void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); //构造函数
    testBlock->FuncPtr(testBlock); // 调用 block 执行
    

    首先是testBlock 赋值,其实是调用了__main_block_impl_0 结构体同名的构造函数,内部对implDesc进行了赋值操作,重点是impl.FuncPtr = fpfp 就是外界传入的__main_block_func_0

    然后是testBlock的调用,将 testBlock强制转换为__block_impl*,并调用了FuncPtr(),也就是__main_block_func_0 ()函数,也就执行了整个block

    所以block,在编译时被包装成了一个__main_block_impl_0的结构体,并执行了同名的构造函数,将 block 块转换成__main_block_func_0 函数作为参数传入,同时还传入了一些描述信息。执行 block时,转为调用FuncPtr()函数,即__main_block_func_0函数。

    4. block变量捕获

    我们定义一个int变量a,并在block 中调用:

    int main(){
        int a = 10;
        void(^testBlock)(void) = ^{
            printf("%d",a);
        };
        testBlock();
        return 0;
    }
    

    编译成cpp 文件,查看源码:

    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;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
    
            printf("%d",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 a = 10;
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
        return 0;
    }
    

    从上面可以看到__main_block_impl_0结构体中多了一个参数a,并且构造函数中也多了一个参数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;
      }
    };
    

    说明程序在编译时,自动将外部变量a捕获并添加到了__main_block_impl_0结构体内,并且将参数a的值赋值给结构体中的变量 a

    我们继续看下__main_block_func_0函数:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
    
            printf("%d",a);
        }
    

    可以看到这里输出的a,并不是 block外界的那个a,而是来自于__cself->a,也就是__main_block_impl_0结构体中的int a 成员变量。这样也就解释了为什么没有__block修饰的变量无法在block内部修改,因为它们完全不是同一个东西。

    我们现在用__block 修饰 int a

    int main(){
        
        __block int a = 10;
        void(^testBlock)(void) = ^{
            a++;
            printf("%d",a);
        };
        testBlock();
        return 0;
    }
    

    编译后查看源码:

    // 变量 a 被包裹成一个结构体
    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)++;
            printf("%d",(a->__forwarding->a));
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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 a变量为__Block_byref_a_0结构体对象
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        // 这里将包装后的 __Block_byref_a_0 结构体传入__main_block_impl_0构造函数
        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);
        return 0;
    }
    

    我们发现变量int a 被包装成了一个__Block_byref_a_0结构体对象:

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

    并且这个结构体内部保存了变量 a指针地址和值,然后将__Block_byref_a_0对象传入__main_block_impl_0()构造函数中:

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

    之后在__main_block_func_0函数中,通过变量 a 的指针地址修改a的值:

    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)++;
            printf("%d",(a->__forwarding->a));
        }
    

    这里的a++操作,是对a->__forwarding->a的操作,即外界的那个变量a

    总结:

    • 使用__block修饰外部变量,外部变量会被包装成一个__Block_byref_a_0结构体对象;
    • 结构体用来保存原始变量的指针和值
    • 将变量生成的结构体对象的指针地址传给 block,然后在block内部就可以对外界变量进行操作了。

    相关文章

      网友评论

          本文标题:[iOS] block分析

          本文链接:https://www.haomeiwen.com/subject/jgdvzktx.html