zoukankan      html  css  js  c++  java
  • 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.总结

  • 相关阅读:
    HDU5418.Victor and World(状压DP)
    POJ2686 Traveling by Stagecoach(状压DP)
    POJ3254Corn Fields(状压DP)
    HDU5407.CRB and Candies(数论)
    CodeForces 352D. Jeff and Furik
    CodeForces 352C. Jeff and Rounding(贪心)
    LightOj 1282 Leading and Trailing
    Ural 1057. Amount of Degrees(数位DP)
    HDU 2089 不要62 (数位DP)
    HDU5366 The mook jong (DP)
  • 原文地址:https://www.cnblogs.com/feng9exe/p/7485615.html
Copyright © 2011-2022 走看看