iOS底层-Block系列一

Block的本质

简介

OC中的Block定义

  1. LLVM Block_private.h可以找到苹果关于block的相关定义。
  2. 仅仅看一些关键代码如下:

     /* Revised new layout. */
     struct Block_descriptor {
         unsigned long int reserved;
         unsigned long int size;
         void (*copy)(void *dst, void *src);
         void (*dispose)(void *);
     };  
     struct Block_layout {
         void *isa;
         int flags;
         int reserved;
         void (*invoke)(void *, ...);
         struct Block_descriptor *descriptor;
         /* Imported variables. */
     };
    
  3. block的底层结构如右图所示 图1
  4. 从上面可以推断
    1. block本质上也是一个OC对象,它内部也有个isa指针
    2. block是封装了函数调用以及函数调用环境的OC对象
  5. 编译分析
    1. OC代码

       int main(int argc, const char * argv[]) {
           @autoreleasepool {
               void (^myblock)(void) = ^{
                   NSLog(@"myblock");
               };
                      
               myblock();
           }
           return 0;
       }
      
    2. 通过编译后,取出核心代码如下:注意这里从C++角度分析!!!

        //结构体__block_impl对象
        struct __block_impl {
            void *isa;
            int Flags;
            int Reserved;
            void *FuncPtr;
        };
        //C++中的对象,就是那个结构体的原型
        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; //block的类型
                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_t2_d94848jx1fq4vd6k8qcsfdq00000gn_T_main_448c61_mi_0);
        }
               
        //创建一个静态结构体变量__main_block_desc_0_DATA并初始化
        static struct __main_block_desc_0 {
            size_t reserved;//保留字
            size_t Block_size;//block的大小
        } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
               
        //main函数
        int main(int argc, const char * argv[]) {
            { __AtAutoreleasePool __autoreleasepool;
               
                //因此block本质是一个__main_block_impl_0结构体对象指针
                void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
                //函数调用:把myblock转化成__block_impl* 类型,取出FuncPtr成员函数,然后调用
                ((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);
            }
       return 0;
       }
      
      1. block本质就是一个__main_block_impl_0类型的结构体对象指针
      2. 疑问难点:

         ((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);
        
        1. myblock表面上是一个函数指针,但本质上是一个__main_block_impl_0结构体对象类型的指针
        2. 为何可以强制转换成__block_impl对象指针,而且可以直接从里面取出成员FuncPtr呢?
        3. 原因分析:
          1. ` __main_block_impl_0对象内部的第一个成员是__block_impl的impl`,根据对象在内存中存储的本质,对象的地址 == 第一个成员变量的地址
          2. 因此__main_block_impl_0对象的地址,就是他的第一个成员变量的地址,因此可以直接强制转换,而且去取成员变量不会报错。

block的变量捕获(capture)

  1. 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
    1. 局部变量
      1. 自动变量(auto):会捕获到block内部,但是捕获到的仅仅是变量的值,值传递
        1. 自动变量的内存随时会销毁,所以不能捕获地址!!!
      2. 静态变量(static):会捕获到block内部,但是捕获到的是变量的地址,指针传递
    2. 全局变量
      1. 不会捕获到block内部,使用时直接可以访问。
  2. 举例论证:
    1. OC代码:

       //全局变量
       int g_var = 9;
              
       int main(int argc, const char * argv[]) {
           @autoreleasepool {
               //自动变量 auto
               int age = 10;
                      
               //static变量
               static int height = 20;
               void (^myblock)(void) = ^{
                   //将外部的age捕获进来
                   NSLog(@"====%d===%d====%d",age,height,g_var);
               };
               age = 20;
               height = 30;
               g_var = 10;
               //打印 ====10===30====10
               myblock();
           }
           return 0;
       }
      
      
    2. 编译成C++,核心代码

       int g_var = 9;
       struct __main_block_impl_0 {
           struct __block_impl impl;
           struct __main_block_desc_0* Desc;
           //age成员,值
           int age;
           //height成员,注意这里是指针!!!
           int *height;
           //构造函数,带有初始化列表,默认参数
           __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
               impl.isa = &_NSConcreteStackBlock; //block类型
               impl.Flags = flags;
               impl.FuncPtr = fp;
               Desc = desc;
           }
       };
              
       static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
                  
           //值传递
           int age = __cself->age; // bound by copy
           //指针传递
           int *height = __cself->height; // bound by copy
           //__cself取值age 、 *height
           //g_var直接取值
           NSLog((NSString *)&__NSConstantStringImpl__var_folders_t2_d94848jx1fq4vd6k8qcsfdq00000gn_T_main_f0817d_mi_0,age,(*height),g_var);
       }
              
       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[]) {
           /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
              
               int age = 10;
              
              
               static int height = 20;
               void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
               age = 20;
               height = 30;
               g_var = 10;
              
               ((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);
           }
           return 0;
       }
      
  3. self的捕获

     #import "Person.h"
     @implementation Person
     //这么会捕获self吗?
     //会的,因为当前的test函数,本质是objc_msgSend(self,@selector(test));,self作为局部变量传递到函数中去的,因此self是局部变量,而且是指针类型
     -(void)test{
         void(^block)(void) = ^{
             NSLog(@"====%@",self->_name);
         };
         block();
     }
     @end
    

block的三种类型

  1. 常见的变量有哪些?
    1. 在C语言中常见的变量如下:
      1. 自动变量(Auto),也可以称为局部变量
      2. 函数参数(形参)
      3. 静态变量(局部变量用static修饰)
      4. 静态全局变量(全局变量用static修饰)
      5. 全局变量
    2. 而在OC中常用的,变量除了函数参数以外,其他都用到了
  2. block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
    1. 全局block: __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
    2. 栈block: __NSStackBlock__ ( _NSConcreteStackBlock )
    3. 堆block:__NSMallocBlock__ ( _NSConcreteMallocBlock )

    图1

  3. block的继承体系

     void test(){
         void(^block)(void) = ^{
             NSLog(@"=======block=");
         };
         //既然block也是一个OC对象,那么我们可以查看他的父类
         NSLog(@"=======block=%@===",[block class]);
         NSLog(@"=======block=%@===",[block superclass]);
         NSLog(@"=======block=%@===",[[block superclass] superclass]);
         NSLog(@"=======block=%@===",[[[block superclass] superclass] superclass]);
            
     }
    
    1. 打印:

       =======block=__NSGlobalBlock__===
       =======block=__NSGlobalBlock===
       =======block=NSBlock===
       =======block=NSObject===
      
    2. 继承体系:__NSGlobalBlock__ :__NSGlobalBlock:NSBlock:NSObject

  4. 三种block的特点: 图1

    1. _NSConcreteStackBlock
      • 特点:存放在栈区,生命周期由系统控制的,一旦返回之后,就被系统销毁了。
      • 栈block的表现形式:
      • 条件1:拥有局部变量(自动变量)或者成员属性变量(即使被strong或者copy修饰的成员变量)
      • 没有被强引用
      • 如下面的例1:
    2. _NSConcreteGlobalBlock
      • 特点:这种Block存储在程序的数据区域(跟函数存储在一起),生命周期从创建到应用程序结束。全局block的copy是个空操作,实际上就相当于单粒.
      • 全局block的表现形式:
      • block中没有用到任何block内部以外的变量
      • block内部仅仅用到了全局变量/静态全局变量,静态变量
      • 如下面的例2
    3. _NSConcreteMallocBlock
      • 特点:存放在堆区,没有强指针引用即销毁,生命周期由程序员控制
      • 堆block的表现形式:
        • 堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)
      • 在ARC环境下,以下几种情况,编译器会自动的判断,把Block自动的从栈copy到堆
        • 手动调用copy的栈block
        • 栈Block被强引用,被赋值给__strong或者id类型
        • copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,这可以归纳为上一条
        • Block是函数的返回值
        • 调用系统API入参中含有usingBlcok的方法
        • 举例3:
    4. 例1:stackBlock

       #import "TestObject.h"
              
       @interface TestObject ()
       {
           int b;
       }
       @property (nonatomic,copy) NSString *str;
       @end
              
       @implementation TestObject
              
       - (void)test {
                  
           int i = 10;
           //注意,这个block可是srong类型,OC中定义一个变量默认情况下是__strong类型
           //堆block
           //        void (^block)() = ^{i;};
           //栈block
           __weak void (^weakBlock)() = ^{i;};
           // 创建时,都会在栈中
           NSLog(@"%@", ^{i;});
           // 获取了该对象的成员属性变量
           NSLog(@"%@", ^{b;});
           //被copy修饰的str
           NSLog(@"=====%@", ^{ _str;} );
           // 如果是weak类型的block
           NSLog(@"%@", weakBlock);
       }
       @end
      
      1. 打印结果如下:

         <__NSStackBlock__: 0x7fff5b3600f0>
         <__NSStackBlock__: 0x7fff5b3600b8>
         <__NSStackBlock__: 0x7fff5b360118>
         =====<__NSStackBlock__: 0x7fff5594c0a8>
        
    5. 例2:两种globalBlock

       #import <Foundation/Foundation.h>
              
       int global_i = 1;
       static int static_global_j = 2;
              
       int main(int argc, const char * argv[]) {
                  
           static int static_k = 3;
           //globalBlock1:类型1
           void (^myBlock)(void) = ^{
               NSLog(@"Block中 变量 = %d %d %d",static_global_j ,static_k, global_i);
           };
           myBlock();
           NSLog(@"myBlock===%@",myBlock);
                  
           //globalBlock2:类型2
           void (^block2)(void) = ^{
                  
           };
           NSLog(@"block2===%@",block2);
           return 0;
       }
          
      
      1. 打印结果:

         Block中 变量 = 2 3 1
         myBlock===<__NSGlobalBlock__: 0x10cc6b050>
         block2===<__NSGlobalBlock__: 0x10cc6b090>
        
    6. 堆block

       #import "TestObject.h"
              
       @interface TestObject ()
       {
           int b;
       }
       @property (nonatomic,copy) void (^tesblock)();
       @property (nonatomic,copy) NSString *str;
       @end
       @implementation TestObject
       - (void)test {
           int i = 10;
           // 1.手动调用copy的栈block
           NSLog(@"%@", [^{i;} copy]);
           //注意,这个block可是srong类型,OC中定义一个变量默认情况下是__strong类型
           //2.栈Block被强引用,被赋值给__strong或者id类型
           void (^block)() = ^{i;};
           NSLog(@"block===%@", block);
           //3.copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock
           self.tesblock = ^{
               //如果没有这个i,那就是全局block,即使用copy引用,也是全局block,从而也说明全局block是不可囊转化成堆block
               i;
           };
            NSLog(@"tesblock===%@", self.tesblock);
           //4.Block是函数的返回值
           NSLog(@"block3===%@", [self test3]);
           //5.调用系统API入参中含有usingBlcok的方法(这个还不懂,以后有空在怼吧)
       }
       - (void(^)())test3 {
           //如果是全局block那么,即使是函数的返回值,他仍然还是全局block
           //return ^{};
           int a;
           return ^{a;};
       }
              
       @end
      
      1. 打印结果:

         <__NSMallocBlock__: 0x6000002425b0>
         block===<__NSMallocBlock__: 0x60400025c1a0>
         tesblock===<__NSMallocBlock__: 0x60400025c290>
         block3===<__NSMallocBlock__: 0x60400025c3b0>
        

block作为函数的参数

  1. 当Block为函数参数的时候,需要我们手动copy一份到堆上

     - (void)test {
         int i = 10;
         [self test4:^{i;}];
     }
     - (void)test4:(void (^)())sblock {
         //sblock是一个栈block,需要copy操作
          NSLog(@"sblock===%@", [sblock copy]);
     }
        
    
Table of Contents