美文网首页iOS小记iOS 知识点今日看点
详细的探讨一下Block(讨论篇、基础篇、实质篇)

详细的探讨一下Block(讨论篇、基础篇、实质篇)

作者: DrunkenMouse | 来源:发表于2016-11-10 17:15 被阅读1330次

    章节目录

    • 关于Block的讨论篇
    • Block的基础篇
    • Block的实质篇
    • 讨论篇:

    为什么要看Block?
    1. 为了更熟练地使用Block
    • 为了扩展程序编程的思维

    • 了解底层的运作原理,有助于与他人探讨相关问题

    • 公认的大神中基本都会,作为一个菜鸟要多向大牛学习

    • 很多面试都需要

    • 基础篇:

    什么是block?

    Block 一个带有自动变量的匿名函数。
    匿名函数是因为Block没有函数名称,由^ 返回值类型 入参类型 表达式 组成。但可以赋值给Block类型变量。
    自动变量在Block中表现为截获自动变量值,指Block内部调用外部变量时会捕获该变量在此瞬间的值。

    Block长什么样?

    通常: ^ 返回值类型 参数类型 表达式,如: ^int(int count){return count + 1; }
    但无论是整型还是无返回值,返回值类型都可省略为: ^ 参数列表 表达式
    省略返回值,根据表达式中有无return决定是否有返回值,如果有则返回该返回值类型,如果没有就是无返回值。所以,所有return返回值类型都必须相同。
    如:^(int count){return count + 1;}
    若没有参数,则参数类型也可省略,简化为:表达式。如:{NSLog(@“DrunkenMouse”);}

    Block类型变量

    int(^blk)(int) ; blk就是Block类型变量

    简化Block声明与调用

    typedef int (^blk_t)(int); 则用blk_t声明就代表此block
    如int类型a 为 int a; 则int (^blk_t)(int)类型的blk 为 blk_t blk;
    而调用则就 int result = blk(10);

    __block说明符

    若无__block修饰,则Block语法中只能捕获博并保存调用瞬间时的外部自动变量值,且不支持修改。
    若想要修改外部自动变量就需要添加__block修饰符,此时此自动变量可称为__block变量。
    但是对于OC对象来说,调用变更该对象的方法不会发生错误。
    如NSMutableArray的对象array,调用其addObject方法修改其值时不会发生错误。但若对array进行赋值操作就会发生错误。
    也就是赋值会报错,但使用不会报错。
    另外,Block中无法截获C语言中的数组。因此对于C语言数组而言,无论调用,还是赋值都会报错。

    • 实质篇:

    Block是作为C语言源码来处理的。通过支持Block的编译器,将Block源码转换成C语言编译器能够处理的源代码,而后作为极为普通的C语言源码进行编译。

    可手动通过clang的-rewrite-objc转换为C++源码。说是C++,其实只是使用了C++中的struct结构的C语言源码。

    Block是OC对象,其结构体内部的isa指针放置该Block的信息的地址,形同OC对象中的元类

    一个无参无反的最简单Block

    void (^blk)(void) = ^{printf(“Block\n”)};
    blk();
    

    通过clang转换后会包括一大串代码,而这一大串代码等稍后总结时再看,现在先看下源码中的各个结构体:

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

    此结构体包含当前Block的实例地址与相关信息。
    首先可以看到其内部有两个结构体,struct __block_impl impl 与 struct __main_block_desc_0 *Desc

    __block_impl结构体的声明为

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

    存储着一个isa,指向一个存储着该Block信息的地址
    Flags 一个标志
    Reserved 该Block升级后所存放的区域
    void *FuncPtr 一个无返回值的函数指针,指向由当前Block语法指向的C语言函数指针

    __main_block_desc_0结构体的声明为:

    struct __main_block_desc_0 {
        unsigned long reserved;
        unsigned long Block_size;
    }
    

    这个结构体保存今后版本升级所需的区域和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;
    }
    

    注意! 这里用到了结构体 __block_impl
    isa,Flags,FuncPtr都存储在struct __block_impl 中

    该构造函数的调用:

    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);
    struct __main_block_impl_0 *blk = &tmp;
    

    意思为将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型变量blk
    对应于我们书写的语句: void (^blk)(void) = ^{printf(“Block\n”);};
    将Block语法生成的Block赋给Block类型变量blk
    加个断句,更加通俗一点的说法:将 Block语法 生成 的Block 赋给 Block类型 变量 blk
    等同于将__main_block_impl_0结构体实例的指针赋给变量blk

    __main_block_impl_0结构体实例构造参数:

    __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    

    第一个参数是由Block语法转换的C语言函数指针
    第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针

    __main_block_desc_0结构体实例的初始化部分代码

    static struct __main_block_desc_0 __main_block_desc_0_DATA = {
        0,
        sizeof(struct __main_block_impl_0)
    };
    

    由此可知,该源代码使用Block,也就是结构体__main_block_impl_0实例的大小进行初始化

    接下来再看看栈上的__main_block_impl_0 结构体实例(即Block)是如何根据这些参数初始化的,先看下该结构体:

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

    优先展开struct __block_impl后,可转换为

    struct __main_block_impl_0 {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
        struct __main_block_desc_0 * Desc;
    }
    

    通常初始化的方式为:

    isa = &_NSConcreteStackBlock;
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    

    相应的作用之前已说过,稍后还会再说一次。这里就不赘述,先往下看吧。

    接下来看一下调用此实例的部分,也就是我们书写的blk();这一行代码,转化成以下源码:

    ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
    

    去掉转换部分:

    (*blk -> impl.FuncPtr)(blk);
    

    简单的使用函数指针调用函数,入参为blk。
    如刚才所说,由Block语法转换的__main_block_func_0 函数的指针被赋值给blk的struct __block_impl的成员变量void * FuncPtr中。
    同时也说明了,__main_block_func_0函数的参数__cself指向Block值(blk)(__cself形同OC中的self,指自身。所以__cself指函数__main_block_func_0)。
    到目前为止也可以看出Block(blk)是作为参数进行传递。

    到此,我们来完整的总结一下关于一个无参无反,只输出一句话的Block

    int main(){
        
        void (^blk)(void) = ^{
            printf("Block");
        };
        blk();
    }
    

    通过clang -rewrite-objc 转换成C语言后的源码摘取部分:

    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");
        }
    
    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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    

    在main函数中,会调用

     void (*blk)(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);
    struct __main_block_impl_0 *blk = &tmp;
    

    将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型变量blk
    而生成__main_block_impl_0结构体实例的第一个参数__main_block_func_0,是一个是由Block语法转换的C语言函数指针,内部就是我们书写的那句输出语句 printf(“Block”);
    而第二个参数&__main_block_desc_0_DATA是struct __main_block_desc_0实例地址(该实例的指针所指向的地址)
    __main_block_desc_0_DATA结构体的初始化是

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

    第一个参数是今后升级所需的区域,为何是0不清楚。
    第二个参数是Block的大小
    struct __main_block_impl_0 相当于当前block,可将此结构体中的struct __block_impl impl展开为:

    struct __main_block_impl_0 {
        void *isa; 
        int Flags;
        int Reserved;
        void *FuncPtr;
        struct __main_block_desc_0 * Desc;
    }
    通常初始化值为:
      isa = &_NSConcreteStackBlock;
      Flags = 0;
      Reserved = 0;
      FuncPtr = __main_block_func_0;
      Desc = &__main_block_desc_0_DATA;
    

    isa指针指向的地址放置该Block的信息,形同OC对象中的元类
    Flags 标志
    Reserved 今后版本升级所存放的区域
    FuncPtr是一个函数指针,指向__main_block_func_0 也就是当前Block语法转换的C语言函数指针
    Desc就是struct __main_block_desc_0 实例,存放struct今后升级所需的区域与Block的大小

    而后是main函数中的第二行

      ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
      去掉转换部分
      (*blk -> impl.FuncPtr)(blk);
    

    简单的使用函数指针调用结构体中的函数,入参为blk
    在函数指针FuncPtr指向的函数中调用printf(“Block”);
    完成Block输出

    本次探讨到此结束,预知后事如何,且听下回分解。

    相关文章

      网友评论

        本文标题:详细的探讨一下Block(讨论篇、基础篇、实质篇)

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