美文网首页
Block探究

Block探究

作者: 飞奔的小鲨鱼 | 来源:发表于2018-12-16 13:52 被阅读0次

    在我们实际的开发过程中,block的使用可以说是经常遇到到的了吧,GCD,网络请求,动画都随处可见block的影子,能熟练的使用block是不是就意味着你真正的了解block呢?正所谓知其然,知其所以然。

    • 基本使用

      block.png

      1.无参无返回值的block

    void (^block)() = ^{
        NSLog(@"无参数无返回值的block");
    };
    block();
    

    2.有参无返回值的block

    void (^block)(int) = ^(int a){
          NSLog(@"有参数无返回值的block  %d",a);
     };
     block(5);
    

    3.有参有返回值的block

    int (^block)(int) = ^(int a){
         NSLog(@"有参数有返回值的block  %d",a);
         return a + 5;
     };
     block(5);
    
    • 作为属性的使用
    typedef void (^sumBlock)(int,int);
    
    @property (nonatomic, copy) sumBlock sumBlc;
    
       self.sumBlc = ^(int a,int b){
            NSLog(@"sum a + b  = %d",a+b);
        };
        
        self.sumBlc(3,5);
    
    • 作为函数调用
    - (void)blockTest{
        NSString *(^stringBlc)(int) = ^(int a){
            // 需要定义的操作
            return [NSString stringWithFormat:@"%dabc",a];
        };
        NSString * str1 = stringBlc(123);
        NSString * str2 = stringBlc(345);
        NSString * str3 = stringBlc(456);
    }
    
    • 底层实现

    简单的介绍了一下block的使用,接下来我们就看看它的底层实现到底是什么样子的,首先创建一个Mac os程序,在main.m函数中写一个简单block

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            void (^block)(int , int) = ^(int a, int b){
                NSLog(@"this is my block a = %d , b = %d",a,b);
                NSLog(@"this age is %d",age);
            };
            block(5,5);
        }
        return 0;
    }
    

    然后进入相应的文件夹,在终端输入clang -rewrite-objc main.m,发现多了一个main.cpp的文件,这是一个9w多行的代码,在文件的最下面我们发现了main函数

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int age = 10;
            void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                                     (void*)__main_block_func_0, 
                                                     &__main_block_desc_0_DATA,
                                                     age)
           //如果block内部没有用到age,只有前两个参数
           //__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DAT)
            );
    
            ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
                                                    (__block_impl *)block, 5, 5);
        }
        return 0;
    }
    

    我们发现__main_block_impl_0这个函数传入了3个参数__main_block_func_0, &__main_block_desc_0_DATA, age,并且将函数的地址赋给了block,那我们看一下__main_block_impl_0

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

    __main_block_impl_0是一个结构体,它里面有两个结构体:impl和 Desc,age 和一个构造函数,并且将传入的__main_block_func_0赋值给了impl.FuncPtr,将传入的&__main_block_desc_0_DATA赋值给了Desc,在构造函数中为age完成了赋值,这一狗仔函数主要是为这两个结构体赋值,那么我们接下来看看这两个结构体。

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

    impl是一个__block_impl类型的结构体,里面有一个isa指针,这个isa的赋值是block的地址,所以说block实质上是一个OC对象,Flags=0,FuncPtr是传入的__main_block_func_0

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

    Desc是一个__main_block_desc_0类型的结构体,里面只有reserved和Block_size,而__main_block_desc_0_DATA的作用就是保存了这个block的大小。

    最后就只剩下__main_block_func_0

    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
      int age = __cself->age; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_0,a,b);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_1,age);
            }
    

    这个函数首先传入了a,b两个参数,从__cself中取出block代码块要用的age,然后是两个NSLog,那我们可以看出这个__main_block_func_0的作用就是保存了block的代码块实现。

    至此,__main_block_impl_0中出现的函数和结构体我们都已经看过了,我们可以大致分析如下:

    • block的底层是一个__main_block_impl_0的函数,这个函数会传入3个参数(没有用到外部变量是2个):分别是__main_block_func_0&__main_block_desc_0_DATA 和用到的外部变量,将__main_block_func_0赋值给impl中的FuncPtr__main_block_desc_0_DATA赋值给Desc,age在构造函数中赋值。
    • __main_block_func_0中保存的是block定义的代码块,__main_block_desc_0_DATA中保存的是block的大小。
     void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                                     (void*)__main_block_func_0, 
                                                     &__main_block_desc_0_DATA,
                                                     age)
    

    它们之间的关系可以用下图表示:


    block的结构图.png

    那我们看看block在这么调用的

    ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
                                                    (__block_impl *)block, 5, 5);
    

    由于block指向的是__main_block_impl_0的地址,而impl是__main_block_impl_0结构体中的第一个成员,里面保存着block的代码块,将block转化为(__block_impl *)类型去调用block的代码块。

    • __block做了什么

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block int age = 10;
            void (^block)(int , int) = ^(int a, int b){
                NSLog(@"this is my block a = %d , b = %d",a,b);
                NSLog(@"this age is %d",age);
                age = 20;
                NSLog(@"this age is %d",age);
            };
            
            block(5,5);
        }
        return 0;
    }
    
    this is my block a = 5 , b = 5
    this age is 10
    this age is 20
    

    我们知道如果age没有用__block修饰的话,在block中直接修改age的值编译器直接会报错,加上就可以直接修改age的值,那__block到底做了些什么?
    将main.m文件clang之后我们发现以下不同:

    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
                                        (void*)0,
                                        (__Block_byref_age_0 *)&age, 
                                         0, 
                                         sizeof(__Block_byref_age_0), 
                                         10
                                  };
    void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                        (void *)__main_block_func_0, 
                                        &__main_block_desc_0_DATA, 
                                        (__Block_byref_age_0 *)&age, 
                                        570425344)
                                  );
    
    ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
                                        (__block_impl *)block, 5, 5);
    
    struct __Block_byref_age_0 {
      void *__isa;          // 0
    __Block_byref_age_0 *__forwarding; // 指向自己的指针
     int __flags;           // 0
     int __size;            // __Block_byref_age_0的大小
     int age;               // age
    };
    

    首先定义了一个__Block_byref_age_0类型的结构体age,__forwarding指向结构体age自己,里面封装了外部变量age。

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

    __main_block_impl_0内部有一个__Block_byref_age_0类型的age,在构造函数中_age->__forwarding指向了结构体自己。

    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
      __Block_byref_age_0 *age = __cself->age; // bound by ref
    
           NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_0,a,b);
           NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_1,
                      (age->__forwarding->age));
           (age->__forwarding->age) = 20;
           NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_2,
                      (age->__forwarding->age));
            }
    

    首先取出__main_block_impl_0中的结构体age,在修改age的值得时候,先通过__forwarding找到结构体age的地址,再去修改age中真正的age对象的值。

    static void __main_block_copy_0(struct __main_block_impl_0*dst, 
                                    struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 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};
    

    因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 __main_block_desc_0结构体中间增加成员变量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)void (*dispose)(struct __main_block_impl_0*),利用OC的Runtime进行内存管理。
    这里的_Block_object_assign_Block_object_dispose就对应着retain和release方法。

    _Block_object_assign函数调用时机及作用

    当block进行copy操作的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部会调用_Block_object_assign函数。
    _Block_object_assign函数会自动根据__main_block_impl_0结构体内部的age是什么类型的指针,对age对象产生强引用或者弱引用。

    _Block_object_dispose函数调用时机及作用

    当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。
    _Block_object_dispose会对age对象做释放操作,类似于release,也就是断开对age对象的引用,而age究竟是否被释放还是取决于age对象自己的引用计数。

    iOS底层原理总结 - 探寻block的本质
    深入研究Block捕获外部变量和__block实现原理

    相关文章

      网友评论

          本文标题:Block探究

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