iOS_Block

作者: 自律_自强_通达 | 来源:发表于2018-09-06 16:01 被阅读0次
    • 定义:带有自动变量(局部变量)匿名函数

      • 匿名函数:顾名思义,所谓匿名函数就是不带有名称的函数。
    • block语法:

      • 完整形式的block定义与一般C函数定义相比,仅有两点不同:
        • 没有函数名:因为它是匿名函数,所以没有函数名。
        • 带有“^”:“ ^ ” (插入记号,caret)记号,在大量使用block的时候方便查找。
      • 语法格式
        ^ 返回值类型 参数列表 表达式
        返回值类型和参数列表可以省略。
    • block类型变量

      • 声明block类型变量
        int (^ blk) (int)
      • block 变量和C变量完全相同,可以作为以下用途使用:
        • 自动变量
        • 函数参数
        • 静态变量
        • 静态全局变量
        • 全局变量
      • 那么下面我们讲block赋值为block类型变量:
        int (^ blk) (int) = ^ (int count) {return count + 1;};
        int (^ blk1) (int) = blk;
        int (^ blk2) (int);
        blk2 = blk1;
    • “带有自动变量”究竟是什么呢?
      这句话在blocks中表现为“截获自动变量”,“截获”的含义是,当block执行时,自动变量的瞬间值,会被保存下来,在block中使用。即使后面代码中,该自动变量被重新赋值也不不会影响block中的值。

    • __block说明符
      实际上被截获的自动变量值,在block中是不能被改写的,如果要改写,这时就需要在前面加上“__block”

    • block的实质
      block是“带有自动变量的匿名函数”,那么block究竟是什么呢?答:OC对象。

      • 分析: block实际上是作为极普通的C语言代码来处理的,通过支持block的编译器,block部分代码会被转换成一般C语言编译器能够处理的源代码,并作为C语言源代码被编译。实际操作是 clang(LLVM编译器)通过“-rewrite-objc” 选项将block部分的代码转换成C++代码,这里只是使用了C++的struct结构,本质还是C语言源代码。
        clang -rewrite-objc 源代码文件名

    下面我们转换block语法。

    int main (){
      void (^blk) (void) = ^{printf("Block \n");};
      blk();
      return 0;
    }
    

    此源代码的block语法最为简单,它省略了返回值类型以及参数列表。该源代码通过clang可转变为以下形式:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    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){
      printf("block \n");
    };
    
    static struct __main_block_desc_0 {
      unsigned long reserved;
      unsigned long Block_size;
    } __mian_block_desc_0_DATA = {
      0,
      sizeof(struct __main_block_impl_0)
    };
    
    int main () {
      void (*blk) (void) = (void (*) (void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
      ((void (*) (struct __block_impl *))((struct __block_impl * )blk)->FuncPtr)((struct __block_impl *) blk);
      return 0;
    } 
    

    5行代码增加到了 35行,但是这段代码并不复杂。
    下面,我们逐个分解代码:
    分解前:

    ^{printf("block\n");};
    

    分解后,里面也有对应代码。

    static void __main_block_func_0 (struct __main_block_impl_0 *__cself){
      printf("block \n");
    }
    

    如变换后的源代码所示,通过blocks使用的匿名函数,实际上被作为简单的C语言函数来处理。另外,根据block所属的函数名(此处为main)和该block在该函数出现的顺序值(此处为0)来给经clang变换过的函数命名

    该函数的参数__cself相当于C++实例方法中指向实例自身的变量this,或者是OC实例方法指向对象自身的变量self,即参数__cself指向block值的变量。

    不过本次这个__main_block_func_0 函数并不使用__cself。我们来看下参数的声明:

    struct __main_block_impl_0 * __cself
    

    与C++的this 和 OC的self相同,参数__cself 是__main_block_impl_0 结构体的指针。

    该结构体声明如下:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0 * Desc;
    }
    

    由于转换后的源代码中,也一并也写入了该构造函数中,所以看起来稍显复杂,如果去掉该构造函数,__main_block_impl_0 结构体会变得非常简单。第一个成员变量impl,我们先看下__block_impl结构体的声明:

    struct __block_impl {
      void *isa;
      int Flags;// 某些标志
      int Reserved;// 今后版本升级所需的区域
      void *FuncPtr;// 函数指针
    };
    

    我们从其名称可以联想到某些标志、今后版本升级所需的区域以及函数指针。第二个成员变量时Desc指针,以下为其 __main_block_desc_0 结构体声明:

    struct __main_block_desc_0 {
      unsigned long reserved;// 今后版本升级所需区域
      unsigned long Block_size; // Block的大小
    };
    

    这些也如其成员变量所示,其结构为今后版本升级所需区域和Block的大小。
    那么,下面我们来看下初始化这些结构体的 __main_block_impl_0 结构体的构造函数。

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

    以上就是初始化__main_block_impl_0结构体成员的源代码。我们刚刚跳过了_NSConcreteStackBlock 的说明。_NSConcreteStackBlock用于初始化__block_impl结构体的isa成员。在了解它之前,我们先看下该构造函数的调用。

    // 此处为了方便阅读,给代码折行了。
    void (*blk) (void) =
             (void (*) (void)) &__main_block_impl_0(
                      (void *)__main_block_func_0, &_main_block_desc_0_DATA);
    

    因为转换较多,所以,我们去掉转换的部分,具体如下:

    struct __main_block_impl_0 tmp = 
            __main_block_impl_0 (__main_block_func_0, &__main_block_desc_0_DATA);
    __main_block_impl_0 *blk = &tmp;
    

    这样就容易理解了。该源代码将__main_block_impl_0 结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例指针,赋值给__main_block_impl_0指针类型的变量blk。以下这部分代码对应最初的源代码。

    void (^blk) (void) = ^{printf("block\n");};
    

    那么,现在我们下结论,block就是带有isa指针和其它成员的结构体。
    和OC的对象本质是一样的。如果你想知道OC对象的本质,可以百度以下,这个地方暂时不做讲解。

    相关文章

      网友评论

          本文标题:iOS_Block

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