理解Block
概念
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:
-
_NSConcreteGlobalBlock
全局的静态block,不会访问任何外部变量。 -
_NSConcreteStackBlock
保存在栈中的block,当函数返回时会被销毁。 -
_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的实现,从中我们可以看出:
- 一个block实际上就是一个对象,它主要是由一个isa、一个imp 和一个descriptor组成。
- 在本例中,isa指向_NSConcreteGlobalBlock
- impl是实际的函数指针,它指向__globalBlock_block_func_0。这里的impl相当于之前提到的invoke变量,只是clang编译器堆变量的命名不一样而已。
- 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 };
本例中我们可以看到:
- isa指向
_NSConcreteStackBlock
,说明这是一个分配在栈上的实例。 -
__main_block_impl_0
中增加了一个变量a,在block中引用的变量a实际上是在申明block时,被复制到__main_block_impl_0结构体中的那个变量a,因为这样,我们就能理解,在block内部修饰变量a的内容,不会影响外部的实际变量a。 -
__main_block_impl_0
中由于增加了一个变量a,所以结构体的大小变大了,该结构体大小被写在了__main_block_desc_0中。
最后看_NSConcreteMallocBlock
的实现,可以看这里;
整体参考这里;