• block总结


    3.编译器中的block

    3.1 block的数据结构定义

    我们通过大师文章中的一张图来说明:

    block-struct.jpg

    上图这个结构是在栈中的结构,我们来看看对应的结构体定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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. */
    };

    从上面代码看出,Block_layout就是对block结构体的定义:

    isa指针:指向表明该block类型的类。

    flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。

    reserved:保留变量,我的理解是表示block内部的变量数。

    invoke:函数指针,指向具体的block实现的函数调用地址。

    descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。

    variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。

    3.2 block的类型

    block有几种不同的类型,每种类型都有对应的类,上述中isa指针就是指向这个类。这里列出常见的三种类型:

    _NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:

    1
    2
    3
    4
    5
    #include int main()
    {
        ^{ printf("Hello, World! "); } ();
        return 0;
    }

    _NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:

    1
    2
    3
    4
    5
    6
    #include int main()
    {
        char a = 'A';
        ^{ printf("%c ",a); } ();
        return 0;
    }

    _NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void exampleB_addBlockToArray(NSMutableArray *array) {
        char b = 'B';
        [array addObject:^{
                printf("%c ", b);
        }];
    }
    void exampleB() {
        NSMutableArray *array = [NSMutableArray array];
        exampleB_addBlockToArray(array);
        void (^block)() = [array objectAtIndex:0];
        block();
    }

    总结一下:

    _NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。

    _NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。

    _NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。

    3.3 编译器如何编译

    我们通过一个简单的示例来说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #import typedef void(^BlockA)(void);
    __attribute__((noinline))
    void runBlockA(BlockA block) {
        block();
    }
    void doBlockA() {
        BlockA block = ^{
            // Empty block
        };
        runBlockA(block);
    }

    上面的代码定义了一个名为BlockA的block类型,该block在函数doBlockA中实现,并将其作为函数runBlockA的参数,最后在函数doBlockA中调用函数runBloackA。

    注意:如果block的创建和调用都在一个函数里面,那么优化器(optimiser)可能会对代码做优化处理,从而导致我们看不到编译器中的一些操作,所以用__attribute__((noinline))给函数runBlockA添加noinline,这样优化器就不会在doBlockA函数中对runBlockA的调用做内联优化处理。

    我们来看看编译器做的工作内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #import __attribute__((noinline))
    void runBlockA(struct Block_layout *block) {
        block->invoke();
    }
    void block_invoke(struct Block_layout *block) {
        // Empty block function
    }
    void doBlockA() {
        struct Block_descriptor descriptor;
        descriptor->reserved = 0;
        descriptor->size = 20;
        descriptor->copy = NULL;
        descriptor->dispose = NULL;
        struct Block_layout block;
        block->isa = _NSConcreteGlobalBlock;
        block->flags = 1342177280;
        block->reserved = 0;
        block->invoke = block_invoke;
        block->descriptor = descriptor;
        runBlockA(&block);
    }

    上面的代码结合block的数据结构定义,我们能很容易得理解编译器内部对block的工作内容。

    3.4 copy()和dispose()

    上文中提到,如果我们想要在以后继续使用某个block,就必须要对该block进行拷贝操作,即从栈空间复制到堆空间。所以拷贝操作就需要调用Block_copy()函数,block的descriptor中有一个copy()辅助函数,该函数在Block_copy()中执行,用于当block需要拷贝对象的时候,拷贝辅助函数会retain住已经拷贝的对象。

    既然有有copy那么就应该有release,与Block_copy()对应的函数是Block_release(),它的作用不言而喻,就是释放我们不需要再使用的block,block的descriptor中有一个dispose()辅助函数,该函数在Block_release()中执行,负责做和copy()辅助函数相反的操作,例如释放掉所有在block中拷贝的变量等。

    4.总结

  • 相关阅读:
    【GitHub】上传代码通用操作等(附下载单个文件夹或文件)
    【Git】之分支合并命令
    【FFmpeg】之Mac系统爬取所有M3U8视频下载方法
    i2c超时
    linux下串口调试
    cgminer分析
    i2c驱动理解
    STM32(三十七)SPI读取W25Q128flash的厂商ID、设备ID以及读写数据(硬件SPI)
    驱动静态和动态加载
    I2C基本原理及对I2C Adapter的理解
  • 原文地址:https://www.cnblogs.com/feng9exe/p/7485615.html
Copyright © 2020-2023  润新知