理解Block

作者: 43b86d3b5040 | 来源:发表于2017-04-08 17:43 被阅读15次

    概念

    Block是Cocoa和Cocoa框架的匿名函数的实现,所谓匿名函数,就是一段具有对象性质的代码段,一方面这段代码可以当作函数来执行,另一方面,由可以当作对象来进行传递,所以可以让某代码段变成某个对象的属性,或是当作方法或是函数的参数传递,也是因为这种特性,我们使用block来实现回调。

    语法

    • 定义成变量
    returnType (^blockName)(parameterTypes) = ^returnType(parameterTypes){...};
    
    eg:
    
    void (^BlockA)(int a) = ^void(int a)
        {
            
        };
        BlockB(1);
    
    • 定义成property
    @property (nonatomic, copy)returnType (^blockName)(parameterTypes);
    
    eg:
    
    @property (nonatomic, copy)int (^BlockA)(int a);
    
    self.BlockA = ^int(int a){....};
    self.BlockA(1);
    
    
    • 定义成方法的参数
    - (void)methodName:(returnType(^)(parameterTypes))blockName{}
    
    eg:
    
    - (void)test:(int(^)(int a)) blockA
    {
        blockA(1);
    }
    
    [self test:^int(int a){
        
    }];
    
    
    • 定义成typedef类型
    typedef returnType(^BlockName)(parameterTypes);
    
    eg:
    
    typedef int(^BlockA)(int a);
    @property (nonatomic, copy)BlockA blockA;
    
    self.block = ^int(int a)
    {
    };
    self.block(1);
    
    

    什么时候使用block,什么时候使用delegate

    如果我们调用一个方法,这个方法只有一个单一回调,那么久使用block,如果可能有多个不同的回调,那就使用代理。

    对block的深入理解

    block内部结构

    block-struct

    对应的结构体定义如下:

    
    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实例实际上有6部分组成。

    • isa指针: 所有对象都有该指针,用于实现对象相关的功能
      isa是一个Class类型的指针,它指向对象的类,而Class里也有个isa的指针,指向meteClass(元类),元类保存了类方法的列表,当类方法被调用时,会先从本身查找类方法的实现,如果没有,元类会向它的父类查找该方法。

    • flags: 用于按bit位表示block的附加信息,本文后面极少block copy的实现代码可以看到对该变量的使用。

    • reserved: 保留变量。

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

    • descriptor: 表示该block的附加描述信息

    • variables: capture过来的变量,block能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

    在Objective-C语言中,一共有3种类型的block:

    1. _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量。
    2. _NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
    3. _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0的时候会被销毁。

    下面我们用clang工具去看一下block的实现。

    首先看一下_NSConcreteGlobalBlock

    新建文件 globalBlock.c

    void (^globalBlock)() = ^{};
    int main()
    {
        return 0;
    }
    
    

    使用命令 clang -rewrite-objc globalBlock.c即可在目录中看到clang输出的一个名为globalBlock.cpp的文件,该文件就是block在c语言的实现,将无关代码去掉,

    
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct `__globalBlock_block_impl_0` {
      struct __block_impl impl;
      struct __globalBlock_block_desc_0* Desc;
      `__globalBlock_block_impl_0`(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteGlobalBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __globalBlock_block_func_0(struct `__globalBlock_block_impl_0` *__cself) {
    }
    
    static struct __globalBlock_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __globalBlock_block_desc_0_DATA = { 0, sizeof(struct `__globalBlock_block_impl_0`)};
    static `__globalBlock_block_impl_0` __global_globalBlock_block_impl_0((void *)__globalBlock_block_func_0, &__globalBlock_block_desc_0_DATA);
    void (*globalBlock)() = ((void (*)())&__global_globalBlock_block_impl_0);
    int main()
    {
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    
    
    

    下面我们就具体看一下是如何实现的。
    __globalBlock_block_impl_0 就是该block的实现,从中我们可以看出:

    1. 一个block实际上就是一个对象,它主要是由一个isa、一个imp 和一个descriptor组成。
    2. 在本例中,isa指向_NSConcreteGlobalBlock
    3. impl是实际的函数指针,它指向__globalBlock_block_func_0。这里的impl相当于之前提到的invoke变量,只是clang编译器堆变量的命名不一样而已。
    4. descriptor是用于描述当前block的附加信息的,包括结构体的大小,需要capture和disponse的变量列表等,结构体大小需要保存是因为,每个block因为会capture一些变量,这些变量会加到__globalBlock_block_impl_0结构体中,使其体积变大。

    其次看一下_NSConcreteStackBlock的实现

    另新建文件StackBlock.c

    int main()
    {
        int a = 100;
        void (^block)(void) = ^{
            printf("%d\n",a);
        };
        block();
        
        return 0;
    }
    
    

    同样适用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;
      int a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
    
            printf("%d\n",a);
        }
    
    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()
    {
        int a = 100;
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    
    

    本例中我们可以看到:

    1. isa指向_NSConcreteStackBlock,说明这是一个分配在栈上的实例。
    2. __main_block_impl_0中增加了一个变量a,在block中引用的变量a实际上是在申明block时,被复制到__main_block_impl_0结构体中的那个变量a,因为这样,我们就能理解,在block内部修饰变量a的内容,不会影响外部的实际变量a。
    3. __main_block_impl_0中由于增加了一个变量a,所以结构体的大小变大了,该结构体大小被写在了__main_block_desc_0中。

    最后看_NSConcreteMallocBlock的实现,可以看这里;

    整体参考这里;

    相关文章

      网友评论

        本文标题:理解Block

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