一、什么是Blocks
Blocks是C语言的扩充功能,可以用一句话表示这个功能:带有自动变量(局部变量)的匿名函数
编译后:就是在文件中的一个函数。
在C中可能使用的变量
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
其中,在后三种,在文件中的任何地方都能访问到。
研究Blocks的重点就是:在Blocks中,怎么访问、存储、改变前两种数据。
“带有自动变量值的匿名函数”这一概念并不仅指Blocks,还存在与其他编程语言,也被称为:
- Block:
C + Blocks
、Smalltalk
、Ruby
- 闭包(Closure):
swift
- lambda计算(λ计算,lambda calculus等):
LISP
、Python
、C++ 11
- Anonymous function(匿名函数):
JavaScript
二、Block类型变量
完整形式的Block类型变量定义语法 与 C语言函数定义,仅有两点不同:
- 没有函数名:因为是匿名函数
- 带有 ^ :返回值类型钱带有’^’(插入记号,caret)记号,因为OS X、iOS中大量使用Block,便于查找
- 不完整形式的Block类型变量:可以省略返回值类型、参数列表
2.1 C 函数指针
1 2 3 4
| int func (int count) { return count + 1; } int (*funcptr) (int) = &func;
|
2.2 OC Block
在Block语法下,可将Block语法赋值给 Block类型的变量。
在Block中的文档中,“Block”既指源代码中的Block语法,也指由Block语法生成的值。
比较:
- 相比C而言,仅仅是将* 改成了 ^
- 调用起来没有区别
三、Block的实质
3.1 代码反编译看Block
clang(LLVM 编译器)具有转换为我们可读源代码的功能,我们可以通过-rewrite-objc
将含有Block语法的源代码变换为C++的源代码,本质上是C语言源代码:
1
| clang -rewrite-objc 源代码文件名
|
代码转换:
1 2 3 4 5
| int main(){ void(^blk)(void)=^{ printf("Block\n");}; blk(); return 0; }
|
源代码通过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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| struct __block_impl { void *isa; int Flags; int Reserved; vold *Funcptr; }
struct __main_block_impl_0 { struct __block_impl imp1; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { imp1.isa = &_NSConcreteStackBlock; imp1.Flags = flags imp1.FuncPtr = fp; Desc = desc: } };
static void __main_block_func_0(struct __main_block_impl_0* cself) { printf("Block\n"); }
static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
int main() {
void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
( (void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr )((struct __block_impl *)blk);
return 0; }
|
3.2 &_NSConcreteStackBlock 是什么
在讲 &_NSConcreteStackBlock
与 isa
之前,先了解一下 class
、id
这两个关键字的定义:
1 2 3 4 5 6 7 8 9
| struct objc_class { Class isa; } typedef struct objc_class * class;
struct objc_object{ Class isa; } typedef struct objc_object * id;
|
isa:是一个Class 类型的指针.
- 每个实例对象有个isa的指针,他指向对象的类Class
- Class里也有个isa的指针, 指向meteClass(元类)。
- 元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).
- 根元类的isa指针指向本身,这样形成了一个封闭的内循环。
元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。
3.3 小结
- 每个对象、类本质上都是结构体,都有isa指针。
- 每个结构体都持有对象、类的属性、方法的名称、方法的实现(函数指针)、以及父类的指针。
&_NSConcreteStackBlock
相当于Block isa指向的类,在将Block作为OC对象处理时,关于该类的信息放置于_NSConcreteStackBlock
中。
总结:Block即为Objective-C的对象,C中的结构体,底层实现是C语言中的函数。
四、Block的存储域
Block存在三种不同作用域的对象:
- _NSConcreteStackBlock设置在栈上
- _NSConcreteGlobalBlock设置在程序的数据区域(.data区)
- _NSConcreteMallocBlock设置在由malloc函数分配的内存块(即堆)中
4.1 _NSConcreteGlobalBlock
- 像声明全局变量一样声明Block变量时
- 捕获全局变量、静态自动变量、静态全局变量时(此时,如果同时满足
_NSConcreteMallocBlock
的条件,那 _NSConcreteMallocBlock
优先)
- 当不捕获任何自动变量时
4.2 _NSConcreteMallocBlock
- 调用Block的copy实例方法时;
- Block作为函数返回值返回时;
- 将Block赋值给附有__strong修饰符id类型的类或Block类型变量时;
- 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API传递Block时。
调用 objc_retainBlock()
方法,实际上也就是 Block_copy
函数
- 对栈上的Block执行copy方法,会从栈复制到堆;
- 对堆上的Block执行copy方法,引用计数增加;
- 对全局的Block执行copy方法,什么也不会发生。
所以,不管Block配置在何处,用copy方法都不会引起任何问题,不确定存储域的时候可以直接copy。
4.3 _NSConcreteStackBlock
除了以上讲述的情况,其他创建的Block存储于都是在栈上
ARC 无效时,一般需要我们手动将Block从栈复制到堆,然后手动释放。
- -retain
- -copy/Block_copy(block)
- -release/Block_release(block)
对于栈上的block调用retain是无效的,只有先copy到堆上,再copy才会有效果。
五、Block捕获自动变量机制
《Effective Objective-C 2.0》中说道:
Block 默认会把所有捕获的局部变量copy一份到该块所在的内存空间。捕获了多少个变量,就要占据多少内存空间。(注意:拷贝对象时,拷贝的并不是对象本身,而是指向这些对象的指针变量。)
block作用域完成,销毁的时候,会把捕获的对象 release
一次。
5.1 捕获自动变量值
正确理解Block的定义:带有自动变量值的匿名函数.
关于”本质是匿名函数”,上面已经讲过。正确理解”带有自动变量值”,也还是需要从Block的底层实现是函数出发。
5.1.1 捕获规则
Block会自动截获定义语法中所使用到的自动变量(全局变量、静态自动变量、静态全局变量不用截获,因为作用域的原因,可直接使用)的值,即保存该自动变量的瞬间值。
- 定义后(保存后)修改,对调用时没有任何影响。
- 定义中修改自动变量值编译器报错。
捕获非对象自动变量:
捕获对象自动变量:
- 可以使用对象的任何方法(比如:mutableArray的addObject都可以),但是不能赋值变量
- 可以使用、赋值修改对象的属性
注意:
Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。
1 2 3 4
| const char text[] = "hello";
const char * text = "hello";
|
5.1.2 实现原理
实现原理跟C函数的值传递、地址传递结合理解。
捕获自动变量的值的原因是:Block需要保证定义中使用的自动变量在外部随时可能释放,所以Block需要保留该变量(全局、静态因为不会被释放,所以Block对此类变量没有操作)。
1. 没有捕获自动变量时
Block在没有拦截自动变量时的默认形式如下:
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 27 28 29 30 31 32 33 34
| struct __main_block_impl_0 { struct __block_impl imp1; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { imp1.isa = &_NSConcreteStackBlock; imp1.Flags = flags imp1.FuncPtr = fp; Desc = desc: } };
static void __main_block_func_0(struct __main_block_impl_0* cself) { printf("Block\n"); }
static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
|
Block将语法表达式中使用到的自动变量作为成员变量追加到了Block类型变量对应的结构体 __main_block_impl_0
中。
2. 捕获非OC对象自动变量
反编译代码:
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 27 28 29 30 31 32 33 34 35 36
| struct __main_block_impl_0 { struct __block_impl imp1; struct __main_block_desc_0* Desc; const char * fmt; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char * _fmt, int _val, int flags=0) : fmt(_fmt), val(_val) { imp1.isa = &_NSConcreteStackBlock; imp1.Flags = flags imp1.FuncPtr = fp; Desc = desc: } };
static void __main_block_func_0(struct __main_block_impl_0* cself) { const char * fmt = __cself->fmt; int val = __cself->val; printf(fmt, val); }
static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val); ( (void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr )((struct __block_impl *)blk);
|
3. 捕获OC对象自动变量
现状:
- Block将语法表达式中使用到的自动变量作为成员变量追加到了Block类型变量对应的结构体
__main_block_impl_0
中。
- C语言结构体不能含有附有__strong修饰符的变量,因为编译器不知道何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存。
拦截NSArray对象自动变量反编译代码:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| id array = [[NSMutableArray alloc] init]; blk = [^(id obj) { [array addObject: obj]; NSLog(@“array count = %ld”, [array count]); } copy];
blk([NSObject new]);
struct __main_block_impl_0 { struct __block_impl imp1; struct __main_block_desc_0* Desc; id __strong array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong _array, int flags=0): array(_array) { imp1.isa = &_NSConcreteStackBlock; imp1.Flags = flags imp1.FuncPtr = fp; Desc = desc: } };
static void __main_block_func_0(struct __main_block_impl_0* cself, id obj) { id __strong array = __cself->array; [array addObject:obj]; NSLog(@“array count = %ld”, [array count]); }
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 * src) { _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT); }
static void __main_block_dispost_0(struct __main_block_impl_0 * src) { _Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT); }
static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0 *); void(*dispose)(struct __main_block_impl_0 *); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0 };
void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val); ( (void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr )((struct __block_impl *)blk);
|
内部实现:
在 __main_block_desc_0
结构体中增加的成员变量 copy 和 dispose , 以及作为指针赋值给该成员变量 __main_block_copy_0
函数和 __main_block_dispose_0
函数。
- 用途:在上面代码段中有注明
- 调用时机:在上面转换的源码中,没有发现调用。在Block从栈赋值到堆上(
即_Block_copy函数
)以及堆上的Block被废弃时才会
调用这些函数
- 在
__block
变量时,也是这两个方法,不过其中有些不同,对象是BLOCK_FIELD_IS_OBJECT
,__block
变量是 BLOCK_FIELD_IS_BYREF
,通过这两个参数区分两种类型。不过持有、释放时机与机制都是一样的。
这是为什么堆上的Block捕获的对象与 __block
类型变量能超出变量作用域而存在。
5.2 __block存储域说明符
使用附有 __block
说明符的自动变量可在Block中修改、赋值,此类变量称为 __block变量
。
__block
修饰的变量,在block定义后,还可以修改,之后的block调用时,是变量的最新赋值。(因为此时Block捕获的都是地址。)
5.2.1 存储域类说明符
C语言有以下存储域类说明符:
- typedef
- extern
- static 表示作为静态变量存储在数据区中
- auto 表示作为自动变量存储在栈中
- register
__block
说明符类似于static、auto和register,用于指定将变量值设置到哪个存储域中。
5.2.2 实现原理
block拦截 __block
变量与拦截全局、静态全局变量的代码分析:
- 拦截全局、静态全局变量:
- 没有形态改变,在源码中也是以全局变量、静态全局变量形态存在;
- 原处定义相同的代码,
__main_block_func_0
中直接使用变量;
- 其他与默认的Block的源码一样。
- 拦截静态自动变量(与上面看到的拦截自动变量形式一样)。比如:
- 在原处定义static int static_auto_val = 3;
- 在
__main_block_impl_0
中增加了成员变量 int * static_auto_val;
指向该静态变量的指针
- 其他与默认的Block的源码一样。
- 拦截
__block
变量。比如:1 2
| __block int val = 10; __block id __strong() obj = [[NSObject alloc] init];
|
反编译代码:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| struct __Block_byref_val_0 { void * _isa; __Block_byref_val_0 * __forwarding; int __flags; int __size; int val; }
static void __main_block_copy_0 (struct __main_block_impl_0*dst, struct __main_block_impl_0*src){ __Block_object_assign(&dst->val, src-val, Block_FIELD_IS_BYREF); }
static void __main_block_dispost_0(struct __main_block_impl_0 * src) { _Block_object_dispose(src->array, Block_FIELD_IS_BYREF); }
struct __Block_byref_obj_0 { void * __isa; __Block_byref_obj_0 * __forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); __strong id obj; }
static void __Block_byref_id_object_copy_131 (void *dst, void*src){ __Block_object_assign((char *)dst + 40, *(void * *) ((char *)src+40), 131); } static void __Block_byref_id_object_dispose_131(void* src) { _Block_object_dispose(*(void * *) ((char *)src+40), 131); }
static void_main_block_func_0(struct __main_block_impl_0 * __cself){ __Block_byref_val_0 * val = __cself->val; (val->__forwarding->val); }
static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0 *); void(*dispose)(struct __main_block_impl_0 *); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0 };
__Block_byref_val_0 val = { 0, &val, 0, sizeof(__Block_byref_val_0), 10 } __Block_byref_obj_0 obj = { 0, &obj, 0x2000000, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, [[NSObject alloc] init]; } void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
((void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr )((struct __block_impl *)blk);
|
由此可见,block对 __block
修饰的OC对象,与未用 __block
修饰的,在内存管理上几乎是一致的,_Block_object_assign
持有,_Block_object_disposes
释放。 __block
修饰的OC对象,只要 __block
变量还在堆上存在,就不会释放。只不过,__block
修饰的OC对象,不增加引用计数,block不retain变量。
以上对__block
作用域修饰符的底部实现,做了反编译查看,那么 __block
变量具体是怎么运作的呢?
- 在block复制到堆上的时候,
__block
变量也会复制到堆上,在block已经被复制到堆上的时候,再复制,对 __block
变量没有任何影响。当block被废弃的时候, __block
变量也会被释放。
- 当多个block引用
__block
变量时,引用计数增加。与OC的引用计数内存管理完全相同。从编译的源码来看,与block拦截OC自动变量时除了类型 Block_FIELD_IS_BYREF
不一样,其他都一样。
__forwarding
成员变量指向自身,确保 __block
是配置在堆上还是在栈上,都可以通过这个指针正确访问栈上或堆上的 __block
变量。
- 栈上的
__block
变量,在 __block
变量从栈上复制到堆上的时候,会将成员变量 __forwarding
的值替换为复制到的目标堆上的 __block
变量的地址。 这也能解释为什么 __block
修饰过的指针变量(对象名),在block定义后,再打印对象名的地址,发现变为了堆上的地址。
- 栈上的
__block
变量,在block不发生复制,一直在栈上的时候,也不会发生复制,会仍然在栈上,不过此时__forwarding指向自身,会将自身传入block,不跟默认一样,复制指针变量传入。
最后两条也是为什么: __block
修饰的变量可以修改的原因。可以保证 __block
变量在block中的修改,对外部真实有效。
5.2.3 __block与其他所有权修饰符结合
上面,讲的都是 __block
与默认的 __strong
修饰的现象。
__weak
与block的结合使用
- block不管赋值、copy怎么操作,对外部__weak修饰的对象,不持有,这是避免循环引用的机制之一。
__block
与 __weak
同时使用
__block
与 __autoreleasing
不能同时使用。
5.2.4 __block解决循环引用
根本机制是:
- MRC下,对__block修饰的变量,block不进行retain,引用计数不会增加。
- ARC下,
__block
修饰时,可以在block中修改,置为nil,手动解除循环引用,但是比 __weak
的缺点是必须要执行block,要不然不触发置nil行为,还是会循环引用。