说到Block,首先要介绍一下闭包(closure),这是闭包在wiki)中的介绍:

In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function[a] together with an environment.[1] The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.[b] Unlike a plain function, a closure allows the function to access those captured variables through the closure’s copies of their values or references, even when the function is invoked outside their scope.

闭包是一个函数或者是一个带有引用环境的函数,这个环境是一张存有这个函数中所有非本地变量的表

各种编程语言对闭包都有着自己的实现,c#有lambda,js有Closures,C++有函数对象等,而block就是苹果在OS X Snow Leopard和iOS4中开始引入的对C语言的扩展,从此广大开发者在执行数据的回调时又有了多一个选项。而且相对繁琐的delegate,block更加的灵活轻便

接下来我会从几个部分来介绍block

  1. block的内部实现
  2. block的分类
    ###block的内部实现
    LLVM Block_private.h中可以找Block的定义:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /* Revised new layout. */
    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存在着isa指针,我们知道所有实例对象的isa指针都指向了它的类对象,类对象的isa指向了原类对象。这也符合Apple对Block的描述,block也是一个对象,它可以像其他的对象一样存放在Array和Dictionary中

在上述结构体中,最重要的是invoke变量,从声明中可以看出,这是一个函数指针,指向了block的执行代码,block的执行代码就是一个匿名函数,在创建block的时候传递给了invoke变量

Block_layout结构体中包含了一个descriptor变量,而Block_descriptor比较重要的是copy和dispose函数,从字面上来看,copy用于捕获变量并且持有引用,dispose函数用于释放捕获的变量。block捕获的变量都会存放在结构体Block_layout的后面,对于对象存储的指针,在invoke函数执行之前全部读出。

block的分类

block根据内存分步可以分为三类:
NSGlobalBlock 全局的静态block,不会访问任何外部变量
NSStackBlock 保存在栈上的block,当函数返回时会被销毁
NSMallocBlock 保存在堆上的block,当引用计数为0时候会被销毁

你可以使用下边代码创建出这三种block

1
2
3
4
5
6
7
NSString *age = @"11";
NSLog(@"%@", ^{});
NSLog(@"%@", ^{NSLog(@"%@", age);});
void(^block)(void) = ^{
NSLog(@"%@", age);
};
NSLog(@"%@", block);

打印结果:

1
2
3
2019-03-14 14:38:05.435150+0800 Block[5544:702103] <__NSGlobalBlock__: 0x10f6ba180>
2019-03-14 14:38:05.435306+0800 Block[5544:702103] <__NSStackBlock__: 0x7ffee05459f8>
2019-03-14 14:38:05.435437+0800 Block[5544:702103] <__NSMallocBlock__: 0x60000270d5f0>

接下来我们看一下这三类block在内部实现上的差别

NSGlobalBlock的内部实现

这里我们要使用clang命令将Objective-C代码转换成C语言来查看block的源码具体实现

1
clang -rewrite-objc block.m

创建一个GlobalBlock.m文件,打印一个空的blog

1
NSLog(@"%@", ^{});

执行clang -rewrite-objc命令后,在目录下会多一个GlobalBlock.cpp文件,我们去除一些无用的代码,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct __GlobalBlock__testBlock_block_impl_0 {
struct __block_impl impl;
struct __GlobalBlock__testBlock_block_desc_0* Desc;
__GlobalBlock__testBlock_block_impl_0(void *fp, struct __GlobalBlock__testBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __GlobalBlock__testBlock_block_func_0(struct __GlobalBlock__testBlock_block_impl_0 *__cself) {
}
static struct __GlobalBlock__testBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
} __GlobalBlock__testBlock_block_desc_0_DATA = { 0, sizeof(struct __GlobalBlock__testBlock_block_impl_0)};
static void _I_GlobalBlock_testBlock(GlobalBlock * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cy_k1991q191tvgck18lqhnqq7c0000gn_T_GlobalBlock_2850c5_mi_0, ((void (*)())&__GlobalBlock__testBlock_block_impl_0((void *)__GlobalBlock__testBlock_block_func_0, &__GlobalBlock__testBlock_block_desc_0_DATA)));
}

方法 GlobalBlocktestBlock_block_impl_0就是block的实现,我们可以看到 isa指向了 _NSConcreteStackBlock,这里的impl指向了main_block_func_0, main_block_func_0就是创建block时定义的一个函数。因为我们创建的block是空的,所以这里__main_block_func_0也是空的

NSStackBlock的内部实现

创建一个StackBlock.m,添加上以下代码

1
2
NSString *age = @"11";
NSLog(@"%@", ^{NSLog(@"%@", age);});

clang后获取关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct __StackGlobal__testBlock_block_impl_0 {
struct __block_impl impl;
struct __StackGlobal__testBlock_block_desc_0* Desc;
NSString *age;
__StackGlobal__testBlock_block_impl_0(void *fp, struct __StackGlobal__testBlock_block_desc_0 *desc, NSString *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __StackGlobal__testBlock_block_func_0(struct __StackGlobal__testBlock_block_impl_0 *__cself) {
NSString *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cy_k1991q191tvgck18lqhnqq7c0000gn_T_StackGlobal_291f7c_mi_2, age);}
static void __StackGlobal__testBlock_block_copy_0(struct __StackGlobal__testBlock_block_impl_0*dst, struct __StackGlobal__testBlock_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __StackGlobal__testBlock_block_dispose_0(struct __StackGlobal__testBlock_block_impl_0*src) {_Block_object_dispose((void*)src->age, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __StackGlobal__testBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __StackGlobal__testBlock_block_impl_0*, struct __StackGlobal__testBlock_block_impl_0*);
void (*dispose)(struct __StackGlobal__testBlock_block_impl_0*);
} __StackGlobal__testBlock_block_desc_0_DATA = { 0, sizeof(struct __StackGlobal__testBlock_block_impl_0), __StackGlobal__testBlock_block_copy_0, __StackGlobal__testBlock_block_dispose_0};
static void _I_StackGlobal_testBlock(StackGlobal * self, SEL _cmd) {
NSString *age = (NSString *)&__NSConstantStringImpl__var_folders_cy_k1991q191tvgck18lqhnqq7c0000gn_T_StackGlobal_291f7c_mi_0;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cy_k1991q191tvgck18lqhnqq7c0000gn_T_StackGlobal_291f7c_mi_1, ((void (*)())&__StackGlobal__testBlock_block_impl_0((void *)__StackGlobal__testBlock_block_func_0, &__StackGlobal__testBlock_block_desc_0_DATA, age, 570425344)));
}

在这里我们看到impl.isa指向了_NSConcreteStackBlock,StackGlobaltestBlock_block_impl_0中增加了一个变量age,在block中引用的age实际上在申明block时就被就被复制到了StackGlobaltestBlock_block_impl_0结构体中的变量age,心在我们就能够理解,在block内部修改变量age的内容,不会影响外部的实际变量age

在使用block时想要在block内部修改局部变量,需要使用__block来修饰局部变量,此时block的内部实现是怎样的呢
修改StackBlock.h中代码如下:

1
2
3
4
5
6
NSString *age = @"11";
__block NSInteger index = 1;
^{
NSLog(@"%@", age);
index = 2;
}();

重新执行clang,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct __Block_byref_index_0 {
void *__isa;
__Block_byref_index_0 *__forwarding;
int __flags;
int __size;
NSInteger index;
};
struct __StackGlobal__testBlock_block_impl_0 {
struct __block_impl impl;
struct __StackGlobal__testBlock_block_desc_0* Desc;
NSString *age;
__Block_byref_index_0 *index; // by ref
__StackGlobal__testBlock_block_impl_0(void *fp, struct __StackGlobal__testBlock_block_desc_0 *desc, NSString *_age, __Block_byref_index_0 *_index, int flags=0) : age(_age), index(_index->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __StackGlobal__testBlock_block_func_0(struct __StackGlobal__testBlock_block_impl_0 *__cself) {
__Block_byref_index_0 *index = __cself->index; // bound by ref
NSString *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cy_k1991q191tvgck18lqhnqq7c0000gn_T_StackGlobal_274897_mi_1, age);
(index->__forwarding->index) = 2;
}

我们可以看到多了一个Block_byref_index_0结构体,其中包含一个index变量, StackGlobal__testBlock_block_func_0中多了一个__Block_byref_index_0的指针,这样就可以完成修改外部变量

NSMallocBlock的内部实现
NSMallocBlock类型的block在源码中无法体现出来,因为NSMallocBlock是在stackblock在执行copy后才会被拷贝到malloc上,我们可以看一下runtime.c的源码,在这里可以看到block是如何从stack拷贝到malloc的
重点看一下_Block_copy_internal函数中这一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Its a stack block.  Make a copy.
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}

如果判断block位于stack中,则执行copy方法
可以看到在这里讲isa指向了_NSConcreteMallocBlock。此时,位于stack上的block就被转成了NSMallocBlock类型的block

我们可以看到,苹果的网络请求API NSURLConnect还是用的delegate来实现回调,而到了NSURlSession,苹果更多的是换成了Block,更不要说RAC中遍布所有API中的block,所以只有你对Block有着足够深刻的理解,在使用这些框架时候才能更加的游刃有余,做到知其然知其所以然。