前情提要
基于Block原理分析(1),继续分析Block
中的剩余知识点。
1.__block
说明符
我们再来回顾前面截获自动变量的例子。
^{printf(fmt, capturedVariable);};
该源码转换结果如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}
Block
中所使用的的被截获自动变量就如"带有自动变量值的匿名函数"所说,仅截获自动变量的值。Block
中使用自动变量后,在Block
的结构体实例中重写该自动变量也不会改变原来截获的自动变量。详细解释就是截获的自动变量为__cself->fmt
。而这个__cself
就是blk
,而blk
结构体的成员变量fmt
和capturedVariable
是在Block
初始化过程中,以值传递的方式赋值给Block
结构体的成员变量:
//int main 中
int unCapturedVariable = 100;
int capturedVariable = 60;
const char *fmt = "capturedVariable = %d/n";
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));--->Block;
之后再将初始化好的Block
的结构体赋值给blk
:
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
而下面的函数调用(blk->impl.FuncPtr)(blk)
:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
就是把blk
当做参数传递给了__main_block_func_0
方法,所以__cself
就是blk
自己。
如果我们试图在Block
内部改变被Block
截获的值会怎么样呢?系统会产生编译错误。因为在实现上不能改写被截获自动变量值的值,所以当编译器在编译过程中检查出给被截获自动变量值赋值的操作时,便产生了编译错误。这样一来就无法在Block
中保存值了,极为不便。解决这个问题有两种方法。
第一种:C
语言中有三个变量,允许Block
直接改写其值。具体如下:
- 静态变量: 在函数内定义,相比较于自动变量(生存期为函数的生存期),它的生存期为整个程序。但作用域与自动变量相同;
- 静态全局变量: 作用域为定义它的文件内,生命周期为定义它文件的生命周期,大部分情况下同源程序;
- 全局变量: 作用域为全部工程文件, 生命周期为整个源程序的生命周期;
虽然Block
语法的匿名函数部分(__main_block_func_0
)变换为了C
语言函数,但从这个变换的函数中访问静态全局变量,全局变量并没有任何改变,可直接使用。但是静态变量的情况下,转换后的函数原本就设置在含有Block
语法的函数外(静态变量作用域外),所以无法访问, 除非将其捕获。
我们来看看下面这段源代码:
#include <stdio.h>
// 全局变量
int global_val = 1;
// 静态全局变量
static int static_global_val = 2;
int main() {
// 静态局部变量
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
return 0;
}
转换后,摘取有用的部分:
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
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() {
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
return 0;
}
这个结果我们已经很熟悉了,对静态全局变量static_global_val
和全局变量global_val
的访问与转换之前完全相同。静态变量static_val
又是如何转换的呢?以下摘出Block
中使用该变量的部分:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
那这个__cself->static_val
又是谁呢?
int main() {
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
return 0;
}
是Block
初始化的时候的一个参数&static_val
。进行了一个指针传递,这是超出作用域使用变量的最简单方法。可以自己用printf
打印再确认一下。实际上,在由Block语法生成的值Block
上,可以存有超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃,因此Block
中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量。但该变量在Block
内仍然可以访问,这种情况发生在将Block
从栈复制到堆上,与此同时__block
修饰的变量也会一同被复制到堆上, Block
持有该对象。
第二种:使用"__block
说明符"。更准确的表述是:"__block
存储类域说明符"(这个类不是面向对象的类,是类域)。C
语言中有以下存储类域说明符:typedef
,extern
,static
,auto
,register
。
__block
说明符类似于static
,auto
和register
说明符,他们用于指定将变量值设置到哪个存储域中,例如,auto
表示作为自动变量存储在栈中,static
表示作为静态变量存储在数据区内。
下面我们来实际使用__block
说明符,用它来指定Block
中想变更值的自动变量。
.m代码:
#include <stdio.h>
int main() {
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
blk();
return 0;
}
转换后,摘取又用的部分:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t 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};
int main() {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
加一个__block
相较于之前转换的代码出现了很多新面孔,我们一个一个地看。
__block int val = 10;
转换后的代码是:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10};
整体简化一下:
__Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
我们发现,这个自动变量变成了一个结构体实例。__block
修饰的变量(val
)也同Block
一样变成__Block_byref_val_0
结构体类型的自动变量,即栈上生成的__Block_byref_val_0
结构体实例,该变量初始化为10
,并且这个值(10
)也出现在了结构体实例的初始化中,这意味着该结构体持有相当于原自动变量(val
)的成员变量(int val
)。
该结构体声明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
接着,给__block
变量赋值的代码:^{val = 1;};
转换后:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
刚刚在Block
中向静态变量赋值时,使用了指向该静态变量的指针,而向__block
变量赋值要比这个更为复杂。Block
的__main_block_impl_0
结构体实例持有指向__block
变量的__Block_byref_val_0
结构体实例指针(&val)。
__Block_byref_val_0
结构体实例的成员变量__forwarding
持有指向该实例自身的指针(看初始化函数,__forwarding
的赋值就是&val
)。通过成员变量__forwarding
访问成员变量val
。(成员变量int val
是该实例自身持有的变量,它相当于原自动变量val
)。
究竟为什么会有成员变量__forwarding
呢?是为了保证,无论这个被捕获的变量是在栈上还是堆上都能正确地访问这个变量(唯一性)。
另外,__block
变量的__Block_byref_val_0
结构体实例attribute((blocks(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 )&val, 0, sizeof(__Block_byref_val_0), 10};
的初始化并不在Block的__main_block_impl_0
结构体中,而是在int main
中这样做是为了在多个Block
中使用__block
变量。有如下.m源码:
#include <stdio.h>
int main() {
__block int val = 20;
void (^blk0)(void) = ^{
val = 0;
};
void (^blk1)(void) = ^{
val = 1;
};
blk0();
blk1();
return 0;
}
转换后:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 20};
void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
void (*blk1)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));
初始化blk0
,blk1
的代码,都是直接&val(拿的地址),而这个val
是上面创建的__Block_byref_val_0
结构体实例
两个Block
都使用了__Block_byref_val_0
结构体实例val
的地址(&val
),这样就可以在多个Block中使用同一个__block
变量了。当然,反过来一个Block也可以使用多个__block
变量,比如.m:
__block int val = 20;
__block int val1 = 20;
__block int val2 = 20;
void (^blk0)(void) = ^{
val = 0;
val1 = 0;
val2 = 0;
};
转换后:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 20};
__attribute__((__blocks__(byref))) __Block_byref_val1_1 val1 = {(void*)0,(__Block_byref_val1_1 *)&val1, 0, sizeof(__Block_byref_val1_1), 20};
__attribute__((__blocks__(byref))) __Block_byref_val2_2 val2 = {(void*)0,(__Block_byref_val2_2 *)&val2, 0, sizeof(__Block_byref_val2_2), 20};
void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, (__Block_byref_val1_1 *)&val1, (__Block_byref_val2_2 *)&val2, 570425344));
下面我们来了解Block
超出变量作用域仍可存在的理由,以及__block
变量的结构体成员变量__forwarding
存在的理由。讨论之前,我们先来看一下Block
的存储域。
- 通过前面说明可知,
Block(^{printf("This is a Block")};)
转换为Block
的结构体类型(__main_block_impl_0)
的自动变量,__block
变量(__block int val = 20;
)转换为__block
变量的结构体类型(__Block_byref_val_0
)的自动变量。所谓的结构体类型的自动变量,就是栈上生成的该结构体的实例。另外,通过之前的说明可知Block
也是OC
对象。将Block
当做OC
对象来看时,该Block
的类为_NSConcreteStackBlock
。OC
有很多与之类似的类,如:_NSConcreteStackBlock
,_NSConcreteGlobalBlock
,_NSConcreteMallocBlock
。 - 首先我们能够注意到
_NSConcreteStackBlock
中含有stack
一词,即该类的对象Block
设置在栈上。同样地,_NSConcreteMallocBlock
类对象设置在由mallo
函数分配的内存块即堆中,而_NSConcreteGlobalBlock
类对象则设置在程序的数据区域(.data
区)中。 - 到现在为止出现的
Block
例子使用的都是_NSConcreteStackBlock
类,且都设置在栈上,但实际上并非全是这样,在记述全局变量的地方使用Block
语法时,生成的Block
为_NSConcreteGlobalBlock
类对象,例如:void (^blk)(void) = ^{printf("Global Block\n")};
转换后的impl.isa = &_NSConcreteGlobalBlock;
,该Block
的类为_NSConcreteGlobalBlock
类。此Block
设置在程序的数据区域内。因为在使用全局变量的地方不存在自动变量,所以不存在对自动变量进行截获一说。也就是说Block
用结构体实例的内容不依赖于执行时的状态,所以整个程序中只需一个实例。因此,只要不需要用Block
来截获自动变量,就可以将Block
用结构体实例设置在程序的数据区域内。
那么在Block
配置在堆上的_NSConcreteMallocBlock
类对象是在何时使用的呢?这正是上一节最后遗留问题的答案。遗留问题为:Block
超出变量作用域可存在的原因,和__block
变量用结构体成员变量__forwarding
存在的原因。
- 配置在全局变量上的
Block
,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block
。如果其所属的变量作用域结束,该Block
就被废弃。由于__block
变量也配置在栈上,同样地,如果其所属的变量作用域结束。则该__block
变量也会被废弃。 -
Blocks
提供了将__block
变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block
复制到堆上,这样即使Block
语法记述的变量作用域结束,堆上的Block
还可以继续存在。 - 复制到堆上的
Block
将_NSConcreteMallocBlock
类对象写入Block
用结构体实例的成员变量isa
。impl.isa = &_NSConcreteMallocBlock;
- 而
__block
变量用结构体成员变量__forwarding
可以实现无论__block
变量配置在栈上还是堆上,都能够正确地访问__block
修饰的变量。有时在__block
变量配置在堆上的状态下,也可以访问栈上的__block
变量。在此情形下,只要栈上的结构体成员变量__forwarding
指向堆上的结构体实例,那么不管是从栈上的__block
变量还是从堆上的__block
变量都能正确访问。 - 那么
Blocks
提供的复制方法究竟是什么呢?实际上当ARC有效时,大多数情形下编译器会恰当地进行判断,自动生成Block
从栈上复制到堆上的代码。
例:
typedef int (^blk_t)(int);
blk_t testFunc(int rate) {
return ^(int count){return rate * count;};
}
非ARC下编译是不能通过的,会提示:error: returning block that lives on the local stack return ^(int count){return rate * count;};
而ARC环境下的代码会被编译为:
blk_t testFunc(int rate) {
blk_t tmp = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
由objc4
运行时库可知,objc_retainBlock
函数实际上就是__Block_copy
函数。
仔细分析这三步都发生了什么:
1.blk_t tmp = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate);
将通过Block
语法生成的Block(&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate))
赋值tmp
。
2.接下来tmp = objc_retainBlock(tmp)
;相当于tmp = __Block_copy(tmp);
就是把栈上的tmp
也就是原来生成的Block
结构体实例,复制到堆上,再把堆上的这个Block
的地址赋值给tmp
。现在tmp
所指的就是堆上的Block
结构体实例。
3.最后return objc_autoreleaseReturnValue(tmp);
将堆上的结构体实例作为对象,注册到autoreleasepool
中然后返回该对象。即,ARC
下将Block
作为函数返回值,编译器会自动生成复制到堆上的代码。
那什么情况下是编译器不能恰当判断的呢?
就是向方法或函数的参数中传递Block
时。但如果在方法或函数中适当地复制了传递过来的参数(Block
),那么就不必再调用该方法或函数手动复制了。比如Cocoa
框架的方法且方法名中含有usingBlock
的,以及GCD
的API
。其他情况下要记得加copy
,([XXBlock copy];
)。另外对于已配置在堆上和数据区域的Block
调用copy
方法是,前者引用计数会加1,后者什么也不做。但无论Block
配置在何处,用copy
方法都不会引起任何问题。在不确定时,调用copy
方法即可。在ARC
下即便多次调用copy
,编译器也能正确地控制引用计数,所以没问题。
__block变量存储域
若在一个Block
中使用__block
变量,则当Block
从栈上复制到堆上时。这些__block
变量也全部被从栈复制到堆。此时,Block
持有__block
变量。即使在该Block
已复制到堆上的情形下,复制Block
也对所使用的__block
变量没有任何影响。在多个Block
中使用__block
变量时,因为最先会将所有的Bloc
k配置在栈上,所以__block
变量也会配置在栈上。在任何一个Block
从栈复制到堆时,__block
变量也会一并从栈复制到堆并被该Block
所持有。当剩下的Block
从栈复制到堆时,被复制的Block
持有__block
变量,并增加__block
变量的引用计数。如果配置在堆上的Block
被废弃,那么它所使用的__block
变量也就被释放。
聊聊__forwarding
的作用:不管__block
变量配置在栈上还是在堆上,都能够正确地访问该变量。看如下.m代码:
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
NSLog(@"%d", val);
blk();
NSLog(@"%d", val);
//外层++val
++val;
NSLog(@"%d", val);
输出是0 1 2。
转换后的代码:
//初始化__block变量的结构体实例:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
0
};
//初始化Block用结构体并copy再赋值给blk:
void (*blk)(void) = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344)), sel_registerName("copy"));
//执行blk的方法:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
//blk中的方法:
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
++(val->__forwarding->val);
}
//执行外层val++:
++(val.__forwarding->val);
//最后打印val:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xh_7qhjzbrx7zz361c_rtp2lh0w0000gn_T_ViewController_937775_mi_0, (val.__forwarding->val));
当初始化__block
结构体实例的时候,__forwarding
指向的是他自己(val栈
);copy
的时候,会把Block
复制到堆上,同时也把__block
结构体实例复制到堆上,并且栈上的__block
的成员变量__forwarding
会指向堆上的自己(val堆
)。执行Block
内部的++(val(栈
)->__forwarding
->val(堆)
)。执行外层val++
:++(val(栈)
->__forwarding
->val(堆)
)。打印:val(栈).__forwarding->val(堆)
。通过该功能,无论是在Block
语法中,Block
语法外使用__block
变量,还是__block
变量配置在栈上或者堆上,都可以顺利访问同一个__block
变量。
截获对象
.m代码:
typedef void (^blk_t)(id);
blk_t blk;
{
id testArray = [NSMutableArray new];
blk = [^(id obj){
[testArray addObject:obj];
NSLog(@"%lu", (unsigned long)[testArray count]);
} copy];
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
转换后,截取有用的代码:
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
id testArray;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, id _testArray, int flags=0) : testArray(_testArray) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0 (struct __ViewController__viewDidLoad_block_impl_0 *__cself, id obj) {
id testArray = __cself->testArray; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)testArray, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xh_7qhjzbrx7zz361c_rtp2lh0w0000gn_T_ViewController_c6f3a9_mi_0, (unsigned long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)testArray, sel_registerName("count")));
}
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->testArray, (void*)src->testArray, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->testArray, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
//使用部分:
typedef void (*blk_t)(id);
blk_t blk;
{
id testArray = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, testArray, 570425344)), sel_registerName("copy"));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
OC
的运行时库能够准确把握Block
从栈复制到堆以及堆上的Block
被废弃的时机,使用__ViewController__viewDidLoad_block_desc_0
结构体中增加的成员变量copy
和dispose
,以及作为指针赋值给该成员变量的__ViewController__viewDidLoad_block_copy_0
函数和__ViewController__viewDidLoad_block_dispose_0
函数。__ViewController__viewDidLoad_block_copy_0
函数使用_Block_object_assign
函数将对象类型对象赋值给Block
用结构体的成员变量testArray
中并持有该对象。用来管理赋值给Block
用结构体中的testArray
的对象。相当于retain
实例方法,将对象赋值在对象类型的结构体成员变量中。另外_Block_object_dispose
用来释放testArray
中的对象,相当于release
实例方法,释放赋值在Block
用结构体成员变量testArray
中的对象。在Block
从栈上复制到堆时,以及堆上的Block
被废弃时才会调用这两个函数。
Block复制到堆上的时机:
- 调用Block
的copy
方法;
- Block
作为函数返回值时;
- 将Block
赋值给附有__strong
修饰符id
类型的类或是Block
类型成员变量时;
- 在方法名中含有usingBlock
的Cocoa
框架方法或GCD
的API
中传递Block
时;
总结
_Block_copy
函数被调用时Block
被从栈上复制到堆上。通过这种方式,Block
截获的对象就能够超出其变量作用域而存在。在Block
中使用__block
时,也会有这两个方法,不同的是截获对象是:BLOCK_FIELD_IS_OBJECT
,截获__block
自动变量是:BLOCK_FIELD_IS_BYREF
。通过这两个参数区分对象类型还是__block
变量。由此可知,Block
中使用的赋值给附有__strong
修饰符的自动变量的对象和复制到堆上__block
变量由于被堆上的Block
所持有,因而可以超出其变量作用域而存在。
附加:Block的循环引用
typedef void (^blk_t)(void);
@interface MyObject: NSObject {
blk_t blk;
}
@implementation MyObject
- (id)init
{
self = [super init];
blk_ = ^{NSLog(@"self = %@", self)};
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
int main() {
id o = [[MyObject alloc] init];
NSLog(@"%@", o);
return 0;
}
Block
赋值给了对象的成员变量blk_
,相当于该Block
自动调用了copy
方法,Block
被从栈复制到堆上,而且Block
还截获了__strong
修饰符修饰的self
对象,self
一并被复制到堆上并被Block
所持有。因此,对象持有blk_(Block)
,Block
持有对象(MyObject
),形成了循环引用,此时,不会调用对象的dealloc
方法。为避免这种循环引用,id __weak tmp = self
,之后Block
中使用tmp
就没问题了。
另外也可以使用__block
变量来避免循环引用,但需要在Block
体中,给该变量赋值nil
。并执行改Block
。如不执行Block
,将该变量赋值为nil
,仍然会引发循环引用。Block
持有__block
变量,__block
变量持有self
,self
持有Block
。若执行了__block
变量等于nil
,__block
变量就不持有self
了,从而打破循环。
使用__block
打破循环的优点在于执行Block
时可动态决定是否将nil
或是其他对象赋值在__block
变量中。缺点是,为避免循环引用必须要执行Block
。