Blocks的实现
Blocks是“带有自动变量值的匿名函数”。本文通过Blocks的实现来理解Blocks。
本文目录
- Blocks的实质
- 截获自动变量
- 修改Block外部变量的两种方式
- Block存储域
- 截获对象
- __block变量和对象
- Block循环引用
- copy/release
使用工具:clang(LLVM编译器)将OC代码转换成可读的源代码。
clang -rewrite-objc 源代码文件名
Block的实质
Block的实质就是OC对象,Block函数代码实际上被作为简单的C语言函数来处理。
首先写一段最简单的Blocks代码
int main(int argc, const char * argv[]) {
//声明并定义一个block对象
void (^blk)(void) = ^{
printf("Hello,world!\n");
};
//调用block对象
blk();
return 0;
}
转换成C代码之后是这样,Block实际上是由结构体声明的。
struct __block_impl {
void *isa;//这里与OC中类对象一样,指针指向的是类对象
int Flags;//标志
int Reserved;//版本升级所需要的区域
void *FuncPtr;//函数指针
};
static struct __main_block_desc_0 {
size_t reserved;//保留区域
size_t Block_size;//Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//结构体__block_impl和__main_block_desc_0组成了最简单的block结构体__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//构造函数,第一个参数是函数指针,第二个参数是描述信息结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//定义block的类型,总共有三种,全局的静态block,栈中的block,堆中的block。
impl.Flags = flags;//初始化flag
impl.FuncPtr = fp;//传递函数地址
Desc = desc;//初始化__main_block_desc_0结构体
}
};
//block中的函数声明
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello,world!\n");
}
//main函数
int main(int argc, const char * argv[]) {
//定义一个void *的blk指针,等号右边是使用__main_block_impl_0的构造函数进行初始化,传入的第一个参数是函数指针,第二个参数是描述信息结构体
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//调用函数指针,执行函数
//(void (*)(__block_impl *))这部分是将FuncPtr转换成该类型的函数指针,返回值为void *,传参为void,编译器会在传参前添加一个传递结构体自身的指针
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通过上面的源代码,可以很清晰的了解到Block是如何实现的,而在Block的结构体__block_impl中,发现了isa指针,与基于objc_object结构体的OC类对象结构体一样,所以,Block其实就是一个OC对象。
截获自动变量
再来看稍微复杂一点的代码
int main(int argc, const char * argv[]) {
int dmy = 256;//没有被block使用到的变量
int val = 10;//被block捕获的变量
const char *fmt = "val = %d\n";//被block捕获的变量
void (^blk)(void) = ^{
printf(fmt, val);//使用fmt字符串打印val变量
};
//定义完block对象后,首先修改val变量,看修改后block区块里捕获的对象是否也修改
val = 2;
//同样修改fmt指针指向的常量字符串
fmt = "These values were changed. val = %d\n";
//调用block函数blk()
blk();
return 0;
}
转换之后的C代码
struct __main_block_impl_0 {
struct __block_impl impl;//block基本信息结构体
struct __main_block_desc_0* Desc;//描述结构体
//这里可以看到两个函数内的局部变量,被声明到了block的结构体中
const char *fmt;
int val;
//构造函数,其中构造函数也初始化了fmt和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) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block函数定义
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; //拷贝block结构体里的fmt变量
int val = __cself->val; //拷贝val变量
printf(fmt, val);
}
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
//block对象blk的声明和定义,传入函数指针和描述结构体,以及fmt和val两个变量,这里的传值是拷贝
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
printf("val = %d\n", val);
fmt = "These values were changed. val = %d\n";
//调用blk函数
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通过上面的代码,可以发现,Block在函数内捕获的fmt和val两个自动变量均在Block结构体内重新声明了相同类型和名称的变量,并且在构造函数中通过拷贝的方式将值传递进来,也就是说,在定义blk对象的这条语句时,就已经将fmt和val的值传递进了Block结构体实例对象内,此时Block对象中保存的值是此时此刻fmt和val的值的拷贝,之后无论如何修改main函数中fmt和val的内容,都不会影响到block中的保存的fmt和val的副本。
当然,若在函数内创建一个指针变量,例如在上边的代码添加
int *p = &val;//将val的地址赋值给指针p
此时,Block的结构体内也会申请一个int指针p,在构造函数的参数列表,传递的也是指针类型,所以在Block对象里修改p的地址对应的内容时,main函数中的指针p和val的值也会被修改。
在Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,使用指针可以解决该问题。
const char text[] = "Hello";
改成 const char *text = "Hello";
修改Block外部变量的两种方式
Block类型变量
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
如果想修改一个外部变量,有两种方式可以实现。
方法一:静态变量、静态全局变量、全局变量
这三种变量,静态全局变量和全局变量的访问方式没有任何改变,Blocks可以直接使用。静态变量则是通过指针的方式传递。
写一段包括这三种变量的代码
int g_val = 1;//全局变量
static int gs_val = 2;//全局静态变量
int main(int argc, const char * argv[]) {
//静态变量
static int s_val = 3;
//block代码块,截获这三种变量
void (^blk)(void) = ^{
g_val *= 1;
gs_val *= 2;
s_val *= 3;
};
转换后的代码如下:
int g_val = 1;
static int gs_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *s_val;//对于静态变量s_val,使用s_val的指针对其进行访问
//在构造函数里初始化s_val
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_s_val, int flags=0) : s_val(_s_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 *s_val = __cself->s_val; // bound by copy
g_val *= 1;//对全局变量和全局静态变量的访问与转换前没有任何区别
gs_val *= 2;//...
(*s_val) *= 3;//使用指针的方式访问s_val
}
int main(int argc, const char * argv[]) {
static int s_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &s_val));
return 0;
}
对于静态变量s_val,使用指针对其访问,保存静态变量s_val的指针,传递给__main_block_impl_0结构体的构造函数并保存。这是超出作用域使用变量的最简单方法。
而对于自动变量来说,如果使用指针的方式进行访问,当变量作用域结束的同时,原来的自动变量被废弃,此时Block中超过变量作用域的变量则不能通过指针访问,访问结果是未定义的。解决这个问题,则可以使用__block说明符。
方法二:__block存储域类说明符(__block storage-class-specifier)
C语言中有一下存储域类说明符:
- typedef
- extern
- static
- auto
- register
__block说明符类似于static、auto和register说明符,它们用于指定将变量值设置到哪个存储域中。如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区域中。
下面来写一段__block说明符的代码。
int main(int argc, const char * argv[]) {
//为val变量添加__block说明符
__block int val = 10;
//现在可以在Block代码块中为val正确赋值
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
转换后的源代码如下:
//新的结构体,用来保存用__block修饰的对象
struct __Block_byref_val_0 {
void *__isa;//结构体对象
__Block_byref_val_0 *__forwarding;//指向自身
int __flags;
int __size;
int val;//保存被__block修饰的val变量的值
};
//Block的结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; //保存了用__block修饰的变量的结构体
//构造函数,使用_val->__forwarding来初始化val,这个设计的用意在后边说明
__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;
}
};
//Block代码块的内容,该例为对val进行赋值
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(int argc, const char * argv[]) {
//使用__Block_byref_val_0结构体来创建val对象,并对每一个值进行初始化
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
//定义Block对象
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
上面的代码可以看到,使用__block说明符修饰的自动变量,在源代码中实际上是一个__Block_byref_val_0的结构体实例,在__main_block_impl_0中以指针的形式持有,对该变量的修改实际上是通过结构体中的__forwarding指针指向的结构体实例,对val值进行修改。
Block存储域
在之前的代码中,可以看到在__main_block_impl_0结构体的构造函数中,将结构体中的isa变量赋值为_NSConcreteStackBlock,而与之类似的还有_NSConcreteGlobalBlock和_NSConcreteMallocBlock类。
- _NSConcreteStackBlock:该类的对象设置在栈上
- _NSConcreteGlobalBlock:该类对象设置在程序的数据区域(.data区)中
- _NSConcreteMallocBlock:该类对象则设置在由malloc函数分配的内存块(堆)中
其中,当全局变量区域定义Block代码块和Block语法的表达式不使用截获的自动变量时,Block即是_NSConcreteGlobalBlock对象。
虽然通过clang转换的源代码通常是_NSConcreteStackBlock对象,但实现上却有不同。
将Block从栈复制到堆上
当Block语法记述的变量作用域结束时,栈上的Block和__block变量都会被废弃,此时通过Blocks提供的复制方法,将Block和__block变量从栈上复制到堆上,那么即使记述Block变量的作用域结束,堆上的Block还可以继续存在。
复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa。
首先看ARC有效时,自动生成的将Block从栈复制到堆上的代码。
typedef int (^blk_t)(int);
blk_t func(int rate){
return ^(int count){return rate * count;};
}
转换过后的源代码:
blk_t func(int rate){
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
//objc_retainBlock函数实际上就是_Block_copy函数,该函数将栈上的Block复制到堆上,复制后,将堆上的地址作为指针赋值给变量tmp。
//这里tmp是typedef int (^blk_t)(int),它的源代码实际上是typedef int (*blk_t)(int)函数指针,所以可以存储指针。
tmp = objc_retainBlock(tmp);
//将堆上的Block作为Objective-C对象,注册到autoreleasepool中,然后返回该对象。
return objc_autoreleaseReturnValue(tmp);
}
上面是编译器自动生成的代码,而编译器在以下情况无法自动生成复制到堆上的代码
- 向方法或函数的参数传递Block时
以下方法或函数不用手动赋值
- Cocoa框架的方法且方法名中含有usingBlock等时,如NSArray类的enumerateObjectsUsingBlock实例方法以及dispatch_async函数时,但在NSArray的initWithObjects方法中不能自动生成。
- Grand Central Dispath的API
程序员可以手动调用Block的copy方法。
如
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return rate * count;};
blk = [blk copy];
对于在不同区域的Block调用copy方法所进行的动作如下表:
Block的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
不管Block配置在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。
__block变量存储域与__forwarding指针
前面提到,__block对象会被定义为__Block_byref_val_0的结构体实例,并成为__main_block_impl_0结构体实例的一个成员变量,所以当Block被从栈复制到堆上时,__block变量也会受到影响。总结如下表:
__block变量的配置存储域 | Block从栈复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
在一个Block中使用__block变量,当该Block从栈复制到堆时,这些__block变量也全部从栈复制到堆。此时,Block持有__block变量。
__forwarding指针
在__block的结构体中,有一个__forwarding成员变量,在转换成C的源代码中可以看到,所有对__block变量的操作,均是通过__forwarding变量来操作,使用该变量,不管__block变量配置在栈上还是堆上,都能够正确地访问该变量。
最初建__block变量时:
- __forwarding指针指向的是该结构体实例自身。
当Block从栈复制到堆上时, __block变量也从栈复制到堆上,此时:
- 会将成员变量__forwarding的值替换为堆上的__block变量的结构体实例的地址。
- Block语法外的__block变量的__forwarding指针也会指向复制到堆中的__block变量的结构体实例地址。
整个过程如下图所示:

通过该功能,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上或堆上,都可以顺利地访问同一个__block变量。
截获对象
在Block语法中调用语法外的对象,如下代码:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
执行结果为
array count = 1
array count = 2
array count = 3
在执行最后三行代码的时候,array已经跑出了变量作用域,此时array对象被废弃,但结果运行正常。
转换后的源代码如下,这里仅放了部分源代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;//强持有
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
实际上在转换后的源代码的Block用结构体中,声明了一个id array成员对象,默认是__strong修饰,该成员对象以强持有的方式持有Block语法外的array变量,这就保证了在array变量被废弃时,Block的成员对象array仍然持有着NSMutableArray变量,所以代码可以正常运行。
copy和dispose
上面的源代码中新增了两个函数,__main_block_copy_0和__main_block_dispose_0,runtime通过这两个函数在运行过程中将Block从栈复制到堆上以及将堆上的Block废弃。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
这里边的_Block_object_assign函数相当于retain函数,将对象赋值在对象类型的结构体成员变量中。
_Block_object_dispose相当于release函数,释放赋值在对象类型的结构体成员变量中的对象。
copy和dispose函数的调用时机
函数 | 调用时机 |
---|---|
copy函数 | 栈上的Block复制到堆时 |
dispose函数 | 堆上的Block被废弃时 |
Block从栈复制到堆的时机
- 调用Block的copy实例方法时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时
在ARC有效时,上述的2与3情况,编译器会自动地将对象的Block作为参数并调用_Block_copy函数,这与调用Block的copy实例方法效果相同。在4的情况下,在调用的方法内部会对传递来的Block调用Block的copy实例方法或者_Block_copy函数。
这里的第3条,在ARC有效的情况下,如执行这样的语句
void (^blk)(void) = ^{
...
};
NSLog(@"%@", blk);
上面定义的blk对象,实际上就是附有__strong修饰符的id类型,所以NSLog语句执行的结果就是blk是一个NSConcreteMallocBLock。
BLOCK_FIELD_IS_OBJECT
在上面两个函数中,可以注意到一个参数BLOCK_FIELD_IS_OBJECT,在前面使用__block的例子中生成的源代码里,也出现了类似的参数BLOCK_FIELD_IS_BYREF。
- BLOCK_FIELD_IS_OBJECT --> 对象
- BLOCK_FIELD_IS_BYREF --> __block变量
编译器通过该参数区分copy函数和dispose函数的对象类型是对象还是__block变量。
这里通过runtime的源代码中可以查看到枚举的全部定义
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
在ARC有效的情况下,编译器也会有不自动调用copy的情况,除了以下几种情况外,推荐手动调用copy实例方法。
- Block作为函数返回值返回时
- 将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量时
- 向方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时
__block变量和对象
__block说明符可指定任何类型的自动变量。如下例子所示:
__block id __strong obj = [[NSObject alloc] init];
转换后的源代码:
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*);
id obj;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
//dst的地址加40,包括4个指针(isa,__forwarding,两个函数指针)32个字节,两个int为8个字节,将指针移动到obj的起始地址
//131是上面枚举中BLOCK_FIELD_IS_OBJECT和BLOCK_BYREF_CALLER取或的结果
_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);
}
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {
(void*)0,
(__Block_byref_obj_0 *)&obj,
33554432,
sizeof(__Block_byref_obj_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
return 0;
}
在__block变量为附有__strong修饰符的id类型或对象类型自动变量的情形下会发生与在Block中使用附有__strong修饰符的id类型或对象类型自动变量的情况下相同的过程。当__block变量从栈复制到堆时,使用_Block_object_assign函数,废弃时使用_Block_object_dispose函数。
使用__weak说明符的__block变量
当使用__weak说明符修饰的变量在作用域结束后,即是Block代码块中使用了该变量,也会被释放、废弃,变量会变成nil。
__unsafe_retained说明符
__unsafe_retained说明符表明不归编译器管理对象,被它修饰的变量不会像__strong或__weak修饰符那样处理,注意不要通过悬垂指针访问已被废弃的对象。
不能同时使用__autoreleasing和__block
Block循环引用
这个很容易理解,在类中的Block语法中直接调用self或者调用类的成员对象,会导致Block强持有类,但同时类也强持有Block对象,导致循环引用。
解决方法1
在Block语法外声明一个self的弱引用,该方法只在ARC有效的情况下游泳
__weak typeof(self) weakSelf = self;
上述方法让Block以weak的方式持有self,这样就不会引起循环引用,导致内存泄漏。
而这样会引发另一个问题,Block中的代码不是立即执行,在执行的时候可能该weak指针已经被销毁了,所以self会变成nil,这样需要在Block语法内部再添加一局
__strong typeof(weakSelf) strongSelf = weakSelf;
在Block语法内部使用strongSelf来获取self的相关属性和方法,当外部self被release后,strongSelf在block的语法局部还持有该self,当Block语法执行完毕后,strongSelf的生命周期结束被release,此时self的引用计数为0,对象被销毁。
解决方法2
使用__block,在成员方法内
__block id tmp = self;
blk_ = ^ {
NSLog(@"self = %@", tmp);
tmp = nil;
};
但该方法有局限性,必须保证Block对象的代码执行一次。
使用__block变量的优点如下:
- 通过__block变量可控制对象的持有期间
- 在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可
copy/release
在ARC无效时,需要手动将Block从栈复制到堆上,同样需要手动释放Block。
推荐使用copy实例方法代替retain实例方法。
使用release方法释放Block。
同样可以使用C语言的Block_copy函数和Block_release函数,它与copy和release方法效果相同。
ARC无效时的__block
在ARC无效时,__block说明符被用来避免Block中的循环引用。当Block从栈复制到堆时,若Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain(不会被retain则意味着不会导致循环引用),而若没有被__block说明符修饰,则会被retain。