全乎的Blocks讲解
一句话概括Blocks:<strong>带有自动变量(局部变量)的匿名函数</strong>
Blocks模式
Block语法
Block的书写形式:<strong>^ 返回类型 参数列表 表达式</strong>
^int (int count) {
return count + 1;
}
我们可以看到Block的语法和C语言函数相比有两点不同
- 没有函数名
- 带有“^”
省略形式:
1、返回类型。省略返回类型时,如果表达式中有return语句就使用该返回类型,如果表达式没有return语句就是用void类型。表达式中含有多个return语句时,所有return的返回类型必须相同。
2、参数列表。如果使用参数,参数列表也可以忽略。
综上,最简单的Block形式如下:
^{
printf("Blocks\n");
}
Blocks变量
(一)Block类型变量与一般的C语言变量完全相同,可作为一下用途使用:
1、自动变量
2、函数参数
3、静态变量
4、静态全局变量
5、全局变量
当在函数和返回值中使用Block类型变量是,记述方式极为复杂。此时,我们可以像使用函数指针类型那样,使用typedef来解决问题。
typedef int (^blk_t)(int);
void func(int (^blk)(int))//原来的记述方式
void func(blk_t blk) //现在的记述方式
·
int (^func())//原来的记述方式
blk_t func() //现在的记述方式
(二)Block中变量值得获取
Block表达式截获所使用的自动变量的值,即保存自动变量的瞬间值。
(三)__block说明符
默认情况下,执行Block语法时,截获的是自动变量的瞬间值,截获保存之后就不能改写这个值,如果尝试修改这个值,编译器会报编译错误。
如果想在Block语法的表达式中将值赋给Block语法外声明的自动变量,修改在该自动变量上附加__block说明符。
Blocks底层的实现
Block是“带有自动变量的匿名函数”,实际上Block是作为极普通的c语言源代码来处理的。我们先来看一下个最简单的block语法:
int main()
{
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
我们使用clang将objc代码转换为c++的源代码。说是c++,其实也仅是使用了struct结构,本质还是c代码。
//block变量结构体
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;
}
objc中短短的几行代码,转换为c++代码后,竟然增加了这么多。我们从转换后的代码中选取出最重要的这几行代码。下面我们就来分析一下转换的代码,看看block到底是怎么通过c++代码来实现的。
很简单的,第一眼我们就看到转换后的代码中有一行printf("block\n");
,没错,这就是objc代码中的block块内容。发现原来的Block函数,转换成了一个c++函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block\n");
}
函数的名字是根据Block语法所属的函数名和该Block语法在该函数出现的顺序值来给Block函数命名。
此外,我们发现转换的后函数有一个入参__cself
,这个参数就相当于c++实例方法中指向实例自身的变量this或者objc实例方法中指向对象自身的变量self,即参数__cself
为指向Block值得变量。我们先来看看这个参数的声明:
struct __main_block_impl_0 * __cself
__cself
是 __main_block_impl_0
结构体的指针,该结构声明如下:
//去除构造函数
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
__main_block_impl_0
结构体有两个成员变量,一个__block_impl
结构体impl,一个 __main_block_desc_0
结构体指针Desc。一个一个的来看,
先看一下 __block_impl
这个结构体
struct __block_impl {
void *isa; //如同对象类型的Class isa,将Block作为Objc对象是,关于该对象类的信息放置于isa指向的对象中
int Flags; //某些标志
int Reserved; //保留区域
void *FuncPtr; //函数指针
}
我们来分一下这个结构体中的四个参数:
1、isa,我们首先要认识到Blocks在objc也是对象,在Objective-C中使用id这一类型来存储对象。
typedef struct objc_object {
Class isa;
} *id;
objc_object是表示一个类的实例的结构体,id为objc_object结构体的指针类型。
该结构体只有一个字段,即指向 其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对象的selector指向的方法。找到后即运行这个方法。当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。
我们再来看看Class:
typedef struct objc_class *Class;
Class为objc_class结构体的指针类型。objc_class结构体的定义如下:
struct objc_class {
Class isa; //在Objective-c中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,指向metaClass(元类)
·
·
Class super_class; //指向该类的父类,如果该类已经是最顶层的根类,则super_class为NULL
}
在这里我们顺便提一下元类的概念。上面介绍objc_class结构是,提到,所有的类自身也是一个对象,我们可以向这个对象发送消息。如:
NSArray *array = [NSArray array];
在这个例子中,+array消息发送给了NSArray类,而这个NAArray类也是一个对象。既然是对象,那么他也是一个Objc_object指针,他包含一个指向其类的一个isa指针。那么这些就有一个问题了,这个isa指针指向什么呢?为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念:<strong>meta-class是一个类对象的类</strong>.
当我们向一个对象发消息时,runtime会在这个对象所属的这个类的方法列表中查找相应方法,而向一个类发送消息是,会在这个类的meta-calss元类的方法列表中查找。
ps:meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同
实例-类-元类的关系如下图:
</br>
回到正文,__main_block_impl_0结构就相当于objc_object结构体,其内部的isa指针目前一般初始化为
isa = &_NSConcreterStackBlock;
,即该Block类的信息放在了_NSConcreterStackBlock中。2、Flags 标志位,默认设为0
3、Reserved 保留区
4、FuncPtr 函数指针,将Block转换后的函数指针赋值给FuncPtr,供以后调用。
接下来我们来看一下这个__main_block_desc_0
结构体,其声明如下:
static struct __main_block_desc_0 {
size_t reserved; //保留区域
size_t Block_size; //Block的大小
};
说了这么多,我们来看一下objc是怎么把Block转换成c++函数调用的。
首先我们构造过程:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
乍一看,这个函数比较复杂,转换较多,我们分开来看:
struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __mian_block_impl_0 *blk = &tmp;
整个过程就是将__mian_block_impl_0
结构体类型的自我变量,即栈上生成的__mian_block_impl_0
结构体实例的指针,赋值给__mian_block_impl_0
结构体指针类型的变量blk。对应最初源代码:
void (^blk)(void) = ^{printf("Block\n");};
下面就来看看__mian_block_impl_0结构体实例的构造函数:
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)
第一个参数是由Block语法转换后的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0
结构体实例指针。__main_block_desc_0
的初始化如下:
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0) //__main_block_impl_0结构体实例的大小
};
那__mian_block_impl_0
结构体实例具体是如何初始化的呢,我们将__mian_block_impl_0
结构体中的成员变量展开,如下:
struct __mian_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 * Desc;
}
初始化如下:
isa = &_NSConcreterStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
再来看一下调用过程:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉转换部分:
(*blk->FuncPtr)(blk)
就是简单地使用函数指针调用函数。Block语法转换的__main_block_func_0
函数指针被赋值给成员变量FuncPtr。此外,我们也发现了__main_block_func_0
函数的参数__cself
指向Block值。
Block截获自动变量值
上一节我们讲解了Block语法的底层实现,这一节主要讲解如何截取自动变量的值,前面曾提到过,Block获取的是自动变量的瞬间值。和前面一样,我们还使用clang对源代码进行转换。
源代码如下:
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt,val);};
blk();
return 0;
}
转换后的代码:
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;
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));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
与上一节的代码作比较,我们注意到,在__main_block_impl_0
结构体中增加两个变量const char *fmt
、int val
,类型与自动变量的类型完全相同。而且,还发现Block语法表达式中没有使用的自动变量不会被追加。即Blocks的自动变量截获只针对Block中使用的自动变量。
接下来我们再看一下构造函数:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val);
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
可以发现,执行Block语法时,使用自动变量fmt和val来初始化__main_block_impl_0
结构体实例。
再来看一下执行函数:
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);
}
再转换后的函数中,使用的是自动变量保存在__main_block_impl_0
结构体实例中的值。由此也说明了,Block获取的是自动变量的瞬间值。
__block说明符
前面我们说到,Block获取变量时是保存自动变量的瞬间值,在Block内部如果想要改变自动变量的值,编译器会报编译错误。那么如何才能在Block内部实现修改保存自动变量的值呢,在没用使用__block
说明符之前,我们可以根据C语言的特性来实现:
1、静态变量
2、静态全局变量
3、全局变量
我们来看看这段源代码:
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;
}
改源代码里面使用Block改写了静态变量static_val、静态全局变量static_global_val和全局变量global_val。转换后的代码如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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; //静态变量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)); //静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数
return 0;
}
我们看到,对于静态全局变量static_global_val和全局变量global_val,在转换后的函数中直接使用。但是对于静态变量static_val却进行了转换,使用静态变量static_val的指针对其进行访问。将静态变量static_val的指针传递给__main_block_impl_0
结构体的构造函数并保存。这里我们就要问了,为什么自动变量不保存它的指针呢。
在实际使用中,自动变量分配在栈上,在由Block语法生成的Block上,经常情况下Block会在超过其变量作用域的时刻执行,当变量作用域结束时,自动变量就废弃了,通过自动变量的指针去访问已废弃的自动变量,会发生错误。
</br>
Objective-C提供了 __block
说明符来解决这个问题。 __block
说明符用来指定Block中想变更的自动变量。且看下面的源代码:
int main()
{
__block int val = 3; //__block修改val自动变量
void (^blk)(void) = ^{
val *= 1; //修改自动变量的值
};
return 0;
}
转换后的代码如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
·
·
//新增的结构体
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()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
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
变量val变为了一个结构体实例
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
该结构体的初始化过程如下:
__Block_byref_val_0 val = {
0, //__isa
&val, //__forwarding指向自己
0, //__flags = 0
sizeof(__Block_byref_val_0), //__size 为自身__Block_byref_val_0的大小
10, //自动变量的值
}
那么如何给__block
变量赋值呢?
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;
}
通过__main_block_impl_0
结构体中__Block_byref_val_0
结构体实例的指针找到自动变量的结构体实例,然后通过自动变量结构体的成员变量__forwarding
(__forwarding
持有指向该实例自身的指针)访问成员变量val(原变量).
我们为什么是通过成员变量
__forwarding
而不是直接去访问结构体中我们需要修改的变量呢? __forwarding
被设计出来的原因又是什么呢?
Block存储域
我们知道,Block语法块转换为Block结构体类型(函数)的自动变量,__block
变量转换为__block
变量的结构体类型的自动变量。而这两种结构体类型的自动变量,存在于栈上。
通过前面的介绍,我们知道Block也是个Objective-C对象,将Block当作Objective-C对象来看待时,该Block的类为_NSConcreteStackBlock,之前我们提过Block类中有一个isa指针,在初始化时指向_NSConcreteStackBlock
,其实除了这个之外还有两个类似的类:
- __NSConcreteStackBlock
- __NSConcreteGlobalBlock
- __NSConcreteMallocBlock
通过名字我们就可以知道__NSConcreteStackBlock
表示类的对象Block设置在栈上。
__NSConcreteGlobalBlock
表示类的对象Block设置在程序的数据区(.data)中
__NSConcreteMallocBlock
表示类的对象Block设置由malloc函数分配的内存块(堆)中
但是到目前为止,Block使用的都是__NSConcreteStackBlock
类,都是设置在栈上。但是也不全是这样,在记述全局变量的地方使用Block语法时,生成的Block为__NSConcreteGlobalBlock
。此外,如果Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区(使用__NSConcreteGlobalBlock类
)。
那么什么时候使用__NSConcreteMallocBlock
这个类呢。这里我们就来顺便解决一下上面遗留的问题:
- Block超出变量作用域可存在的原因
-
__block
变量用结构体成员变量__forwarding
存在的原因。
我们知道,当一个变量设置在全局区时,从变量的作用于外也可以通过指针安全的使用。但是当设置在栈上时,当作用域结束时,这个Block就被废弃了。___block
变量也是同样的情况。
Blocks提供了将Block和__block
变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。 当Block从栈上复制到堆上时,Block将结构体实例的isa设置成__NSConcreteMallocBlock
我们再来看看__forwarding
成员变量,我们知道,有时候__block
变量配置在堆上,也可以访问栈上的__block
变量。在这种情况下,只要栈上的结构体实例成员变量__forwarding
指向堆上的结构体实例,那么不管是从栈上的__block
变量还是从堆上的__block
变量都能正确的访问。
那么什么时候栈上的Block会复制到堆上呢?
- 调用Block的copy实例方法
- Block作为函数返回值返回时
- 将Block赋值为附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或者Grand Central Dispatch的API中传递Block时。
__block变量存储域
上一节介绍了Block的存储,那么__block
变量又是如何处理的呢,当Block从栈复制到堆时,使用的所有__block
变量也全部被从栈复制到堆。此时,Block持有__block
变量。
在多个Block中使用__block
变量时,因为最先会将所有的Block配置在栈上,所以__block
变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block
变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block
变量,并增加__block
变量的引用计数。
如果配置在堆上的Block被废弃,那么它所使用的__block
变量也就被废弃。
之前我们提到过一句话:“不管__block
变量配置在栈上还是在堆上,都能够访问该变量”。这也就是__forwarding
成员变量存在的原因。
这里引用一下上面出现过的代码:
(val->__forwarding->val) *= 1;
在使用__block
变量时,通过__forwarding
成员变量访问val成员(原变量)。当__block
变量从栈上复制到堆上时,会将__forwarding
成员变量的值替换为复制目标堆上的__block
变量用结构体实例的地址。
ps: 栈上和堆上同时保持__block
变量实例,但是访问和修改值则是在堆上。看张图吧:
截获Block对象
我们看一下源代码:
blk_t blk;
int main()
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
} copy];
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
代码中使用copy函数将Block从栈上复制到堆上,而使用的array这个自动变量也一并复制到堆上。(当ARC有效是,id类型以及对象类型变量必定附加所有权标识符,缺省为附有__strong
修饰符的变量)。Objective-C的运行时库能够准确把握Block从栈上复制到堆上以及Block被废弃的时机,能够在恰当的时机进行初始化和废弃。为此需要使用在__main_block_desc_0
结构体中增加的成员变量copy和dispose,即作为指针赋值给该成员变量的__main_block_copy_0
和__main_block_dispose_0
函数。我们来依次看看这两个函数的具体实现:
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*/);}
_Block_object_assign
函数调用,相当于retain实例方法的函数。将对象赋值在对象类型的结构体成员变量中。有retain方法,肯定有release方法。__main_block_dispose_0
函数调用__main_block_dispose_0
函数释放赋值在Block中的结构体成员变量arrar中的对象。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
__main_block_dispose_0
函数相当于release实例方法函数,释放赋值在对象类型的结构体成员变量中的对象。
有了这种构造,通过使用附有__strong
修饰符的自动变量,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->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};
和Block结构体的部分基本相同,不同支出在于__main_block_copy_0
和__main_block_dispose_0
函数最后的参数不一样。BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF区分对象类型是对象还是__block
变量。
由此可知。Block中使用的赋值为附有__strong
修饰符的自动变量的对象和复制到堆上的__block
变量由于被堆上的Block所持有,因而可超出其变量作用域而存在。
Block循环引用
如果在Block中使用附有__strong
修饰符的自动变量,那么当Block从栈复制到堆上时,该对象为Block所持有,很容易引起循环引用。这种情况经常出现在一个类型对象中,有一个Block类型的成员变量,而这个Block中又引用了self变量,这就会造成一个循环引用。Block持有self,self持有Block。
- (id) init {
self = [super init];
blk = ^{NSLog(@"self = %@",self);
return self;
}
若由于Block引发了循环引用,根据Block的用途选择使用__block
变量、__weak
修饰符或__unsafe_unretained
修饰符来避免循环引用。
网友评论