2.1 Blocks概要
2.1.1 什么是Blocks
Blocks是C语言的扩充功能。是带有自动变量(局部变量)的匿名函数。
匿名函数
C语言的函数
// 声明函数名称为func的函数
int func(int count);
// 通过函数名称func调用函数
int result1 = func(10);
// 将函数func的地址赋值给指针类型变量funcptr
int (*funcptr)(int) = &func;
// 通过函数指针调用函数
int result2 = (*funcptr)(10);
C语言的函数都会必须有函数的名称,而通过Blocks,源代码中能够使用匿名函数,即不带名称的函数。
带有自动变量
C语言的函数中可能使用的变量
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
其中,在函数的多次调用之间能够传递值的变量有:
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
虽然这些变量的作用域不同,但在整个程序中,一个变量总保持在一个内存区域。因此,虽然多次调用函数,但总量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量的值。
Blocks提供了保持自动变量值的方法。
2.2 Blocks模式
2.2.1 Block语法
完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同:
(1) 没有函数名
(2) 带有“^”。
Block语法BN范式
Block_literal_expression ::= ^ block_decl compound_statement_body
block_decl ::=
block_decl ::= parameter_list
block_decl ::= type_expression
🌰
^int (int count){return count + 1;}
返回值类型可以省略。如果声明返回值类型,返回值类型需要跟表达式中return语句中的返回值类型一致,如果表达式中无return语句可以使用void类型。如果表达式中有多个return语句,所有return的返回值类型必须相同。
🌰
^(int count){return count + 1;}
如果不使用参数,参数列表也可以省略。
🌰
^{printf("Blocks");}
2.2.2 Block类型变量
在C语言中,可以将所定义的函数的地址赋值给函数指针类型的变量。同样地,在Block语法下,可将Block语法赋值给声明的Block类型的变量中。
声明Block类型变量的示例如下:
int (^blk)(int);
该Block类型变量与一般C语言变量完全相同,可作为一下用途使用:
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
使用Block语法将Block赋值为Block类型变量
int (^blk)(int) = ^(int count){return count + 1;};
变量blk与通常的变量相同,所以也可以由Block类型变量向Block类型变量赋值,可以向函数传递Block,也可以将Block作为函数的返回值。在函数参数和返回值中使用Block类型变量时,记述方式极为复杂,我们可以像使用函数指针类型时那样,使用typedef来解决该问题。
// 定义一个返回值类型是int 别名为blk_t 有一个int类型参数 的Block
typedef int (^blk_t)(int);
Block类型的变量可完全像通常的C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block指针类型变量。
// C语言中可以这么使用
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1;};
blk_t *blkptr = &blk;
(*blkptr)(10);
2.2.3 截获自动变量的值
Blocks中,Block表达式截获所使用的自动变量的值是自动变量的瞬间值(保存在Block结构体中)。
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt, val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
执行结果
val = 10
2.2.4 __block说明符
自动变量的值截获只能保存执行Block语法的瞬间值。保存后就不能改写该值。
若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上加__block
说明符。
2.2.5 截获的自动变量
赋值给截获的自动变量的操作会产生编译错误,但使用截获的值却不会有任何问题。
赋值操作
id array = [NSMutableArray array];
void (^blk)(void) = ^{
array = [NSMutableArray array];
};
blk();
编译器报错
Variable is not assignable (missing __block type specifier)
使用操作
id array = [NSMutableArray array];
void (^blk)(void) = ^{
[array addObject:@1];
};
blk();
编译器编译成功。
需要注意的是现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,这时使用指针可以解决该问题。
✘
const char text[] = "hello";
void (^blk)(void) = ^{
printf("%c\n",text[2]);
};
blk();
error
Cannot refer to declaration with an array type inside block
√
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n",text[2]);
};
blk();
2.3 Blocks的实现
2.3.1 Block的实质
我们可以通过clang编译器将含有Block语法的源代码转换为C++源代码。
cd到目标文件夹,执行语句
clang -rewrite-objc 源代码文件名
源码
#include <stdio.h>
int main()
{
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
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;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.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 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我们来一步步解读上面的转换后的代码
源代码的Block语法
^{printf("Block\n");}
转换为
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
可以看出Blocks使用的匿名函数实际被作为C语言函数来处理,根据Block语法所属的的函数名(main)和Block语法在该函数出现的顺序值(0)来给clang变换的函数命名。
该函数的参数__self
相当于C++实例方法中指向实例自身的变量this,或是Objective-C实例方法中指向对象自身的变量self,即参数__self
为指向Block值的变量。
这里的参数__self
是__main_block_impl_0
结构体的指针。
综上,Block结构体就是__main_block_impl_0
结构体。Block的值就是通过__main_block_impl_0
构造出来的。
__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;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0
结构体有成员变量impl、Desc和构造函数三部分组成。
__block_impl
结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl
结构体成员变量如下:
- isa:
- Flags:标志
- Reserved:今后版本升级所需的区域
- *FuncPtr:函数指针
__main_block_desc_0
结构体
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__main_block_desc_0
结构体成员变量如下:
- reserved:今后版本升级所需的区域
- Block_size: Block的大小
初始化__main_block_impl_0
结构体的构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
__main_block_imlp_0
结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock
相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现。
Objective-C中由类生成对象就是像结构体这样“生成由该类生成的对象的结构体实例”。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类对的结构体实例指针。
各类的结构体是基于objc_class
结构体的class_t
结构体。class_t
结构体在objc4运行时库的runtime/objc-runtime-new.h中声明如下:
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
}
在Objective-C中,比如NSObject的class_t
结构体实例以及NSMutableArray的class_t
结构体实例等,均生成并保持各个类的class_t
结构体实例。该实例持有声明的成员变量、成员的名称、方法的实现(即函数指针)、属性、以及父类的指针,并被Objective-C运行时库所使用。
如果展开__main_block_impl_0
结构体的__block_impl
结构体,可记述为如下形式:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *Funcptr;
struct __main_block_desc_0* Desc;
};
该结构体构造函数会像下面这样进行初始化
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
Funcptr = __main_block_impl_0;
Desc = & __main_block_desc_0_DATA;
此__main_block_impl_0
结构体相当于基于objc_object
结构体的Objective-C类对象的结构体_NSConcreteStackBlock
相当于class_t
结构体实例。在将Block作为Objective的对象处理时,关于该类的信息放置于_NSConcreteStackBlock
中。
Block实质就是对象。
2.3.2 截获自动变量值
源码
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void(^blk)(void) = ^{printf(fmt,val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
该源码可通过clang转换为以下形式:
struct __main_block_impl_0 {
struct __block_impl impl;
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) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);}
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 dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
fmt = "These values were changed. val = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我们可以看到Block语法表达式中使用的自动变量被作为成员变量追加到__main_block_impl_0
结构体中。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
}
结构体内声明的成员变量类型与自动变量类型完全相同,而Block语法表达式中没有使用的自动变量不会被追加,如源代码中的变量dmy。
总的来说,所谓“截获自动变量的值”意味着在执行Block语法时,Block语法表达式所使用的自动变量的值被保存到Block的结构体实例(即Block自身)中。
2.3.3 __block说明符
当我们尝试像下面这种方式改变Block中的自动变量val时
int val = 0;
void(^blk)(void) = ^{val = 1;};
因为在实现上不能改写被截获的自动变量的值,所以编译器会报错
Variable is not assignable (missing __block type specifier)
而我们有时需要在Block中改变外部变量的值,有两种解决方法。
第一种
C语言中有一个变量,允许Block改写值。具体如下:
- 静态变量
- 静态全局变量
- 全局变量
虽然Block语法的匿名函数部分简单地变换为C语言函数,但从这个变换的函数访问静态全局变量/全局变量没有任何改变,可直接使用。
但是静态变量的情况下,转换后的函数原本就设置在含有Block语法的函数外(使用静态变量的指针进行访问),所以无法从变量作用域访问。
实际上,在由Block语法生成的值Block上,可以存有超过其变量作用域的被截获对象自动变量。变量作用域结束的同时,原来的自动变量被废弃,因此Block中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量。
第二种
使用__bloc
k说明符
C语言中有以下存储类说明符:
- typedef
- extern
- static
- auto
- register
__block
说明符类似于static、auto、register说明符,他们用于指定将变量值设置到哪个存储域中。例如,auto表示作为自动变量存储在栈中,static表示静态变量存储在数据区中。
当我们在自动变量声明上追加__block
说明符
int __block val = 0;
void(^blk)(void) = ^{val = 1;};
该源码可通过clang转换为以下形式:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
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()
{
__Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
}
可以发现__block
变量也同Block一样变成了__Block_byref_val_0
结构体类型的自动变量,即栈上生成的__Block_byref_val_0
结构体实例。该变量初始化为1,且这个值也出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。
该结构体声明如下:
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的__main_block_impl_0
结构体实例持有指向__block
变量的__Block_byref_val_0
结构体实例的指针。
__Block_byref_val_0
结构体实例的成员变量__forwarding
持有指向该实例自身的指针。通过成员变量__forwarding
访问成员变量val。(成员变量val是该实例自身持有的变量,它相当于原自动变量。)
2.3.4 Block存储域
Block转换为Block的结构体类型的自动变量,__block
变量转换为__block
变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。
Block与__block变量的实质
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实质 |
__block变量 | 栈上__block变量的结构体实例 |
Block的类
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域(.data区) |
_NSConcreteMallocBlock | 堆 |
_NSConcreteGlobalBlock
在记叙全局变量的地方使用Block语法时,生成的Block为_NSConcreteGlobalBlock
类对象。例如:
void (^blk)(void) = ^{printf("Global Block");};
int main()
{
该源码通过声明全局变量blk来使用Block语法。如果转换源代码,Block用结构体的成员变量isa的初始化如下:
impl.isa = & _NSConcreteGlobalBlock
该Block的类为_NSConcreteGlobalBlock
类。此Block即该Block用结构体实例设置在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。由此Block用结构体实例的内容不依赖于执行时的状态,所以整个程序中只需一个实例。因此将Block用结构体实例设置在全局变量相同的数据区域即可。
即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block语法不截获自动变量,就可以将Block设置在程序的数据区域。
- 记述全局变量的地方有Block语法时
- Block语法的表达式中不使用应截获的自动变量时
以上两种情况下,Block为_NSConcreteGlobalBlock
类对象。即Block配置在程序的数据区域中。除此之外的Block语法生成的Block为_NSConcreteStackBlock
类对象,且设置在栈上。
_NSConcreteMallocBlock
配置在全局变量上的Block,从变量作用域外可以通过指针安全地使用。但设置在栈上的Block,如果其所属的变量作用域结束,该Block就被废弃。由于__block
变量也配置在栈上,同样地,如果其所属的变量作用域结束,则该__block
变量也被废弃。
Blocks提供了将Block和__block
变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。
复制到堆上的Block将_NSConcreteMallocBlock
类对象写入Block用结构体实例的成员变量isa。
impl.isa = & _NSConcreteMallocBlock;
__block
变量用结构体成员变量__forwarding
可以实现无论__block
变量配置在栈上还是在堆上时都可能够正确地访问__block
变量。
除了向方法或函数的参数传递Block时,编译器都会自动生成将Block从栈上复制到堆上的代码。
但是如果在方法或函数中适当地复制了传递过来的参数,那么就不必在调用该方法或函数前手动复制了。以下方法或函数不用手动复制。
- Cocoa框架的方法且方法名中含有usingBlock等时
- GCD的API
将Block从栈上复制到堆上是相当消耗COU的,当Block设置在栈上也能够使用时,将Block复制到堆上只是在浪费CPU资源。因此只在block作为参数传递时手动调用copy方法。
Block副本
Block的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
2.3.5 __block变量存储域
Block从栈复制到堆时对__block产生的影响
__block变量的配置存储域 | Block从栈复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
若在1个Block中使用__block
变量,Block从栈上复制到堆上,这些__block
变量也全部从栈复制到堆上。此时,Block持有__block
变量。Block已复制到堆上的情形下,复制Block对所使用的__block
变量没有任何影响。
在多个Block中使用__block
变量,任何一个Block从栈复制到堆时,__block
变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block
变量,并增加__block
变量的引用计数。
如果配置在堆上的Block被废弃,那么它所使用的__block
变量就被释放。
使用__block
变量用结构体成员变量__forwarding
的原因
通过Block的复制,__block
变量从栈复制到堆。此时可同时访问栈上的__block
变量和堆上的__block
变量。源代码如下:
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
利用copy方法复制了使用了__block
变量的Block语法。Block和__block
变量均是从栈复制到堆。此代码中在Block语法表达式中使用了初始化的__block
变量。
^{++val;}
然后在Block语法之后使用了与Block无关的变量。
++val;
以上两种源代码均可转换为如下的形式:
++(val.__forwarding->val);
栈上的__block
变量用结构体实例在__block
变量从栈上复制到堆上时,会将成员变量__forwarding
的值替换为复制目标堆上的__block
变量用结构体实例的地址。
这样,无论是在Block语法中、Block语法外面使用__block
变量,还是__block
变量配置在栈或堆上,都可以顺利地访问同一个__block
变量。
2.3.6 截获对象
调用copy函数和dispose函数的时机
函数 | 调用时机 |
---|---|
copy函数 | 栈上的Block复制到堆上时 |
dispose函数 | 堆上的Block被废弃时 |
Block会复制到堆上的时机:
- 调用Block的copy实例方法时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时
截获对象时和使用__block变量时的不同
对象 | BLOCK_FIELD_IS_OBJECT |
---|---|
__block变量 | BLOCK_FIELD_IS_BYREF |
Block中使用对象类型自动变量时,除以下情景外,推荐调用Block的copy实例方法。
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时
2.3.7 __block变量和对象
__block
说明符可指定任何类型的自动变量。
__block id obj = [[NSObject alloc] init];
等同于
__block id __strong obj = [[NSObject alloc] init];
ARC有效时,id类型以及对象类型变量必定附加所有权修饰符,缺省为附有__strong
修饰符的变量。
在__block
变量为附有__strong
修饰符的id类型或对象类型自动变量的情况下,当__block
变量从栈复制到堆上时,使用_Block_object_assign
函数持有赋值给__block
变量的对象。当堆上的__block
变量被废弃时,使用__Block_object_dispose
函数,释放赋值给__block
变量的对象。
由此可知,即使对象赋值复制到堆上的附有__strong
修饰符的对象类型__block
中,只要__block
变量在堆上继续存在,那么该对象就回继续处于被持有的状态。这与Block中使用赋值给附有__strong
修饰符的对象类型自动变量的对象相同。
2.3.8 Block循环引用
如果在Block中使用附有__strong
修饰符的对象类型的自动变量,那么当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");
}
@end
int main()
{
id o = [[MyObject alloc] init];
NSLog(@"%@",o);
return 0;
}
该源代码中MyObject中dealloc实例方法一定没有被调用。
MyObject类对象的Block类型成员变量blk_
持有赋值为Block的强引用。即MyObject类对象持有Block。init实例方法中执行的Block语法使用附有__strong
修饰符的id类型变量self。并且由于Block语法赋值在了成员变量blk_
中,因此通过Block语法声称在栈上的Block此时由栈复制到堆,并持有所使用的self。self持有Block,Block持有self。造成了循环引用。
为了避免此循环引用,可声明附有__weak
修饰符的变量,并将self赋值使用。
- (instancetype)init {
self = [super init];
id __weak tmp = self;
blk_ = ^{NSLog(@"self = %@",tmp);};
return self;
}
也可以使用__block
变量来避免循环引用。
typedef void(^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@implementation MyObject
- (id)init {
self = [super init];
__block id tmp = self;
blk_ = ^{
NSLog(@"self = %@",tmp);
tmp = nil;
};
return self;
}
- (void)execBlock {
blk_();
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main()
{
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
@end
该源代码没有引起循环引用。但是如果不调用execBlock实例方法,即不执行赋值给成员变量blk_
的Block,便会循环引用并引起内存泄露。在生成并持有MyObject类对象的状态会引起一下循环引用。
- MyObject类对象持有Block
- Block持有
__block
变量 -
__block
变量持有MyObject类对象
如果不执行execBlock实例方法,就会持续该循环引用从而造成内存泄露。
通过执行execBlock实例方法,Block被执行,nil被赋值在__block变量tmp中。
blk_ = ^{
NSLog(@"self = %@",tmp);
tmp = nil;
};
因此,__block变量tmp对MyObject类对象的强引用失效。避免循环引用的过程如下所示:
- MyObject类对象持有Block
- Block持有
__block变量
使用__block变量避免循环引用的优点如下:
- 通过
__block
变量可控制对象的持有期间 - 在不能使用
__weak
修饰符的环境中不使用__unsafe_unretained
修饰符即可(不必担心悬垂指针)。在执行Block时可动态地决定是否将nil或其他对象赋值在__block变量中。
使用__block
变量的缺点如下:
- 为避免循环引用必须执行Block
2.3.9 copy/release
ARC无效时,一般需要手动将Block从栈复制到堆。另外,由于ARC无效,所以肯定要释放复制的Block。这时我们用copy实例方法用来复制,用release实例方法来释放。