美文网首页
Objective-C Block篇(一) : Block的实质

Objective-C Block篇(一) : Block的实质

作者: Tenloy | 来源:发表于2018-09-02 11:26 被阅读138次

    本文内容:

    1. 什么是Blocks
    2. Block类型变量
    3. Block的实质(带有(截获)自动变量值的匿名函数)
    4. Block的存储域

    什么是Blocks

    Blocks是C语言的扩充功能,可以用一句话表示这个功能:带有自动变量(局部变量)的匿名函数

    编译后:就是在文件中的一个函数

    # 在C中可能使用的变量

    • 自动变量(局部变量)
    • 函数的参数
    • 静态变量(静态局部变量)
    • 静态全局变量
    • 全局变量
      其中,在后三种,在文件中的任何地方都能访问到。
      研究Blocks的重点就是:在Blocks中,怎么访问、存储、改变前两种数据

    “带有自动变量值的匿名函数”这一概念并不仅指Blocks,还存在与其他编程语言,也被称为:
    Block: C+ Blocks、Smalltalk、Ruby
    闭包(Closure):swift
    lambda计算(λ计算,lambda calculus等):LISP、Python、C++ 11
    Anonymous function(匿名函数):JavaScript

    Block类型变量

    完整形式的Block类型变量定义语法 与 C语言函数定义 仅有两点不同:

    • 没有函数名:因为是匿名函数
    • 带有 ^ :返回值类型钱带有'^'(插入记号,caret)记号,因为OS X、iOS中大量使用Block,便于查找
      不完整形式的Block类型变量:可以省略返回值类型、参数列表

    # C 函数指针 类型变量

    int func (int count)
    {
    return count + 1;
    }
    int (*funcptr) (int) = &func;
    

    # OC Block 类型变量

    在Block语法下,可将Block语法赋值给 Block类型的变量。
    在Blocks中的文档中,“Block”既指源代码中的`Block语法`,也指由Block语法`生成的值`
    int (^blk) (int);  
    

    比较:

    1. 相比C而言,仅仅是将* 改成了 ^
    2. 调用起来没有区别

    Block的实质

    # 代码反编译看Block

    clang(LLVM 编译器)具有转换为我们可读源代码的功能,我们可以通过-rewrite-objc将含有Block语法的源代码变换为C++的源代码,本质上是C语言源代码

    clang -rewrite-objc 源代码文件名
    

    代码转换:

    int main(){
        void(^blk)(void)=^{ printf("Block\n");};
        blk();
        return 0;
    }
    
    源代码通过clang可变换为以下形式:
    
    //从其名称可以联想到某些标志、今后版本所需的区域以及函数指针
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        vold *Funcptr;  
    }
    
    // Block类型变量对应的结构体
    struct __main_block_impl_0 {
        struct __block_impl imp1;   
        struct __main_block_desc_0* Desc;
        //默认构造函数,C++中的语法
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
            imp1.isa = &_NSConcreteStackBlock;
            imp1.Flags = flags  //不传默认=0,Reserved默认也是0
            imp1.FuncPtr = fp;
            Desc = desc:
        }
    };
    
    //该函数的参数__cself相当于C++实例方法中指向实例自身的变量this,或是Objective-C实例方法中指向对象自身的变量self
    //即参数__cself为`指向Block值的变量`
    static void __main_block_func_0(struct __main_block_impl_0* cself)
    {
        printf("Block\n");
    }
    
    //今后版本升级所需的区域和Block的大小
    static struct __main_block_desc_0{
        unsigned long reserved;
        unsigned long Block_size;
    } __main_block_desc_0_DATA = {
        0,
        sizeof(struct __main_block_impl_0)   //block对应结构体的实例大小
    };
    
    int main()
    {
        //创建 构造函数
          /*
            相当于(《OC 高级编程》中写的):
            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;
            相当于源码:void(^blk)(void) = ^{printf("Block\n");};
            问题1:有点搞不懂为什么结构体指针 等价于 下面的函数指针? 有明白的同学,欢迎留言评论
            @param  __main_block_func_0  函数
            @param  &__main_block_desc_0_DATA 结构体指针  "静态全局变量"
          */
        void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);  
    
        //调用
          /*
            问题2:blk明明是__main_block_impl_0结构体类型指针的,为什么变成__block_impl结构体类型指针了?难道又是C C++中的语法?成员变量结构体可以当成外层结构体来用?
          */
        ( (void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr )((struct __block_impl *)blk);
    
        return 0;
    }
    
    

    # &_NSConcreteStackBlock是什么

    在讲&_NSConcreteStackBlockisa之前,先了解一下classid这两个关键字的定义:

    struct objc_class {
        Class isa;
    }
    typedef struct objc_class * class;
    
    struct objc_object{
        Class isa;
    }
    typedef struct objc_object * id;
    
    
    isa:是一个Class 类型的指针. 
    1. 每个实例对象有个isa的指针,他指向对象的类Class
    2. Class里也有个isa的指针, 指向meteClass(元类)。
    3. 元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass). 
    4. 根元类的isa指针指向本身,这样形成了一个封闭的内循环。
    
    元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。
    
    

    综上:

    1. 每个对象、类本质上都是结构体,都有isa指针
    2. 每个结构体都持有对象、类的属性、方法的名称、方法的实现(函数指针)、以及父类的指针
    3. &_NSConcreteStackBlock相当于Block isa指向的类,在将Block作为OC对象处理时,关于该类的信息放置于_NSConcreteStackBlock中.

    总结:Block即为Objective-C的对象,C中的结构体,底层实现是C语言中的函数(下一篇Objective-C Block篇(二) : Block捕获自动变量机制中将通过讲解Block的捕获机制,详细证明Block就是函数)

    Block的存储域

    Block存在三种不同作用域的对象:

    • _NSConcreteStackBlock设置在栈上
    • _NSConcreteGlobalBlock设置在程序的数据区域(.data区)
    • _NSConcreteMallocBlock设置在由malloc函数分配的内存块(即堆)中

    # _NSConcreteGlobalBlock

    1. 像声明全局变量一样声明Block变量时
    2. 捕获全局变量、静态自动变量、静态全局变量时(此时,如果同时满足_NSConcreteMallocBlock的条件,那_NSConcreteMallocBlock优先
    3. 当不捕获任何自动变量时

    # _NSConcreteMallocBlock

    1. 调用Block的copy实例方法时
    2. Block作为函数返回值返回时
    3. 将Block赋值给附有__strong修饰符id类型的类或Block类型变量时
    4. 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API传递Block时

    调用objc_retainBlock()方法,实际上也就是Block_copy函数

    • 对栈上的Block执行copy方法,会从栈复制到堆
    • 对堆上的Block执行copy方法,引用计数增加
    • 对全局的Block执行copy方法,什么也不会发生
      所以,不管Block配置在何处,用copy方法都不会引起任何问题,不确定存储域的时候可以直接copy

    # _NSConcreteStackBlock

    除了以上讲述的情况,其他创建的Block存储于都是在栈上

    ARC 无效时,一般需要我们手动将Block从栈复制到堆,然后手动释放。

    • -retain
    • -copy/Block_copy(block)
    • -release/Block_release(block)

    对于栈上的block调用retain是无效的,只有先copy到堆上,再copy才会有效果

    相关文章

      网友评论

          本文标题:Objective-C Block篇(一) : Block的实质

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