iOS开发

理解Block

2017-04-08  本文已影响15人  43b86d3b5040

概念

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

语法

returnType (^blockName)(parameterTypes) = ^returnType(parameterTypes){...};

eg:

void (^BlockA)(int a) = ^void(int a)
    {
        
    };
    BlockB(1);
@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 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部分组成。

在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的实现,可以看这里;

整体参考这里;

上一篇下一篇

猜你喜欢

热点阅读