美文网首页一步步学习ios
block底层原理学习

block底层原理学习

作者: 冷武橘 | 来源:发表于2020-05-26 16:06 被阅读0次

    一、block的本质

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

    使用clang转换OC为C++代码

    
    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
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_0adf31_mi_0,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 argc, const char * argv[]) {
        int a = 10;
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        return 0;
    }
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    

    通过c++代码可见block内部有个isa指针,本质上也是一个oc对象。

    • block是封装了函数调用以及函数调用环境的OC对象

    二、block的变量捕获

    2.1、值传递

    int main(int argc, const char * argv[]) {
        int a = 10;
        void (^Block)(void) = ^{
            NSLog(@"%d",a);
        };
        return 0;
    }
    
    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;
      }
    };
    int main(int argc, const char * argv[]) {
        int a = 10;
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        return 0;
    }
    

    block在创建时,传入了变量a的值,并将a的值赋给了main_block_impl_0的成员a,整个过程是值传递。

    • 当block内部访问了外部的局部变量(auto
      类型)时,block可以捕获到该局部变量,并以值传递的方式传递给block内部。

    2.1、指针传递

    int main(int argc, const char * argv[]) {
        static  int a = 10;
        void (^Block)(void) = ^{
            NSLog(@"%d",a);
        };
        return 0;
    }
    
    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;
      }
    };
    
    int main(int argc, const char * argv[]) {
        static int a = 10;
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
        return 0;
    }
    

    block在创建时,传入了变量a的地址,并将a的地址赋给了main_block_impl_0的成员*a,整个过程是指针传递。

    • 当block内部访问了外部的局部变量(static
      类型)时,block可以捕获到该局部变量,并以指针传递的方式传递给block内部。

    2.3、全局变量

    static  int a = 10;
    int b = 21;
    int main(int argc, const char * argv[]) {
    
        void (^Block)(void) = ^{
            NSLog(@"%d",a);
            NSLog(@"%d",b);
        };
        return 0;
    }
    
    static int a = 10;
    int b = 21;
    
    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;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_263450_mi_0,a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_263450_mi_1,b);
        }
    
    int main(int argc, const char * argv[]) {
    
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        return 0;
    }
    

    当a,b作为全局变量时,block就不会捕获到内部,直接访问就可以了。


    截屏2020-05-26 上午11.36.58.png

    2.3、测试题

    static  int a = 10;
    int b = 21;
    int main(int argc, const char * argv[]) {
        static c = 22;
        int d = 20;
        void (^Block)(void) = ^{
            NSLog(@"%d",a);
            NSLog(@"%d",b);
            NSLog(@"%d",c);
            NSLog(@"%d",d);
      };
        a = 5;
        b = 5;
        c = 5;
        d = 5;
        Block();
        return 0;
    }
    

    只有c的值还是22,不会被修改,因为block内部捕获到的变量是值传递。

    三、block的类型

    block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型:
    NSGlobalBlock ( _NSConcreteGlobalBlock )
    NSStackBlock ( _NSConcreteStackBlock )
    NSMallocBlock ( _NSConcreteMallocBlock )

    截屏2020-05-26 下午2.00.47.png

    3.1、MRC

    nt main(int argc, const char * argv[]) {
        int a = 10;
        void (^block1)(void) = ^{
            
           NSLog(@"%d",a);
        };
    
        static  int b = 10;
       void (^block2)(void) = ^{
              
           NSLog(@"%d",b);
        };
    
       void (^block3)(void) = ^{
               
           
         };
        void (^block4)(void) = ^{
                  
             NSLog(@"%d",c);
        };
        void (^block5)(void) = ^{
                  
             NSLog(@"%d",d);
        };
        NSLog(@"%@",[block1 class]); __NSStackBlock__
        NSLog(@"%@",[block2 class]);__NSGlobalBlock__
        NSLog(@"%@",[block3 class]);__NSGlobalBlock__
        NSLog(@"%@",[block4 class]);__NSGlobalBlock__
        NSLog(@"%@",[block5 class]);__NSGlobalBlock__
        
        return 0;
    }
    
    • 访问了auto变量,block的类型是_NSStackBlock
    • 没有访问auto变量,block的类型一般是NSGlobalBlock
        int a = 10;
        void (^block)(void) = ^{
            
           NSLog(@"%d",a);
        };
        NSLog(@"%@",[block class]); __NSStackBlock__
        NSLog(@"%@",[[block copy] class]); __NSStackBlock__
       
    
    • NSStackBlock调用了copy后的类型是NSMallocBlock

    每一种类型的block调用copy后的结果如下所示:


    image.png image.png

    3.2、ARC

      static  int b = 10;
         void (^block2)(void) = ^{
                
             NSLog(@"%d",b);
          };
    
         void (^block3)(void) = ^{
                 
             
           };
          void (^block4)(void) = ^{
                    
               NSLog(@"%d",c);
          };
          void (^block5)(void) = ^{
                    
               NSLog(@"%d",d);
          };
          NSLog(@"%@",[block2 class]); __NSGlobalBlock__
          NSLog(@"%@",[block3 class]);__NSGlobalBlock__
          NSLog(@"%@",[block4 class]);__NSGlobalBlock__
          NSLog(@"%@",[block5 class]);__NSGlobalBlock__
    
    • 没有访问auto变量,block的类型是NSGlobalBlock
        int a = 10;
    1、强指针
          void (^block1)(void) = ^{
              
             NSLog(@"%d",a);
          };
     2、弱指针
         __weak void (^block2)(void) = ^{
                 
                NSLog(@"%d",a);
          };
         NSLog(@"%@",[block1 class]);__NSMallocBlock__
         NSLog(@"%@",[block2 class]); __NSStackBlock__
    
     3、没有指针
         NSLog(@"%@", [^{
                     
                    NSLog(@"%d",a);
         }class]);__NSStackBlock__
        
    
    • 在ARC环境下,block被强指针指向时,编译器会根据情况自动将栈上的block复制到堆上。
    • 访问了auto变量,block的类型是_NSStackBlock
    @property (copy, nonatomic) void (^testBlock)(void);
    @property (weak, nonatomic) void (^test1Block)(void);
    @property (strong, nonatomic) void (^test2Block)(void);
    
        int   a = 10;
        self.testBlock = ^(){
            NSLog(@"%d",a);
    
            
        };
        self.test1Block = ^(){
                 NSLog(@"%d",a);
          };
        self.test2Block = ^(){
                 NSLog(@"%d",a);
          };
        NSLog(@"%@",[self.testBlock class]);__NSMallocBlock__
        NSLog(@"%@",[self.test1Block class]);__NSStackBlock__
        NSLog(@"%@",[self.test2Block class]);__NSMallocBlock__
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        self.test1Block();
    
    }
    

    self.test1Block的类型是_NSStackBlock,存储在栈区。过了作用域就会销毁,这样再次调用就会访问了坏内存,造成崩溃。这个例子也说明了MRC下block属性为什么一般用copy,就是 为了内存的访问安全考虑。

    3.3、小结

    • 1、没有访问auto变量,block的类型是NSGlobalBlock

    • 2、 访问了auto变量,block的类型是_NSStackBlock

    • 3、 在ARC环境下,block被强指针指向时,编译器会根据情况自动将栈上的block复制到堆上:
      a、block作为函数返回值时
      b、将block赋值给__strong指针时
      c、block作为Cocoa API中方法名含有usingBlock的方法参数时
      d 、block作为GCD API的方法参数时

    • 4、
      MRC下block属性的建议写法
      @property (copy, nonatomic) void (^block)(void);

      ARC下block属性的建议写法
      @property (strong, nonatomic) void (^block)(void);
      @property (copy, nonatomic) void (^block)(void);

    3.4、对象类型的auto变量

    当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会对auto变量产生强引用。如果block被拷贝到堆上:
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
    如果block从堆上移除,会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的auto变量(release)

    4、__block修饰符

    __block可以用于解决block内部无法修改auto变量值的问题
    _block不能修饰全局变量、静态变量(static)
    编译器会将__block变量包装成一个对象

     __block int a = 10;
          void (^block1)(void) = ^{
              
              a = 0;
          };
    
    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) = 0;
         }
    
    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 argc, const char * argv[]) {
      __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
         void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
       return 0;
    }
    
    本质就是 __block变量被编译器 包装成 __Block_byref_a_0 对象,__main_block_func_0接收的是一个对象,再通过修改对象的指针修改age的值。
    

    相关文章

      网友评论

        本文标题:block底层原理学习

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