__block 说明符
在Block中无法给Block外的自动变量进行赋值,解决这个问题有两个方法
- C语言的静态变量/静态全局变量/全局变量,允许Block改写值。
- 使用__block说明符
第一种代码如下:
int global_val = 1;
static int static_global_val = 2;
int main(int argc, char * argv[]) {
static int static_val = 3;
void (^blk)(void) = ^{
global_val *=1;
static_global_val *=2;
static_val *=3;
};
return 0;
}
复制代码
转换后的代码如下:
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;
__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(int argc, char * argv[]) {
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_val转换后使用静态变量的指针对其进行访问。将静态变量static_val的指针传递给__main_bock_impl_0结构体的构造函数并保存。
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;
}
复制代码
实际上,在由Block语法生成的值Block上,可以存有超过其比变量作用域的被截获对象的自动变量。在变量作用域结束的同时,原来的自动变量被废弃,因为Block中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量。
第二种方法:使用__block 说明符 (__block存储域类说明符) C语言中有以下存储域类说明符:
- typedef
- extern
- static
- auto
- register
引用C语言存储类、链接和内存管理总结一文来解释存储域类说明符
一、存储类 描述一个变量(一个数据对象):存储周期、作用域、链接
作用域:一个C变量的作用域可以是代码块作用域(在函数内部代码块中定义的变量、形参,称为局部变量),函数原型作用域(从变量定义处遗址到原型声明的末尾),或者文件作用域(一个在所有函数之外定义的变量,从定义处到包含定义的文件结尾处都可见,成为全局变量),函数作用域(只适应goto语句使用的标签),函数作用域意味着一个特定函数中的goto标签对该函数中任何地方的代码都是可见的,无论该标签出现在哪一个代码块中。
链接:一个C变量具有一下链接之一,外部链接、内部链接或空链接。
- 具有代码块作用域和函数原型作用域的变量是空链接,由定义所在的代码块和函数原型所私有
- 具有外部链接的变量可以在多文件程序的任何文件使用,多个文件可有共享。
- 具有内部链接的变量可以在一个文件的任何地方使用,归该文件私有。
- 具有文件作用域的变量可能是内部链接或者外部链接。
- 如果一个具有文件作用域的变量用static修饰,则是内部链接,否则是外部链接。
存储时期:一个C变量具有以下两种存储时期之一,静态存储时期(在程序执行期间一直存在)和自动存储时期
- 静态存储时期:在程序执行期间一直存在。具有文件作用域(不管是内部链接还是外部链接)的变量具有此存储周期,注意对于具有文件作用域的变量,static表明链接类型不是存储时期。
- 自动存储时期:具有代码块作用域的变量一般是自动存储周期。
存储类 | 时期 | 作用域 | 链接 | 声明方式 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 空链接 | 代码块内 |
寄存器 | 自动 | 代码块 | 空链接 | 代码块内,使用关键字register |
具有外部链接的静态 | 静态 | 文件 | 外部链接 | 所有函数之外 |
具有内部链接的静态 | 静态 | 文件 | 内部链接 | 所有函数之外,使用关键字static |
空链接的静态 | 静态 | 代码块 | 空 | 代码块内,使用关键字static |
自动变量:用auto修饰或者不修饰就默认属于自动存储类的变量具有自动存储周期、代码块作用域、空链接。在默认下,在代码快或者函数的头部定义的任意变量都属于自动存储类。
如果在内层中使用和外层一样名字的变量,则内层会覆盖外层变量,在程序离开内层代码块时,外部变量就恢复了其作用和之前的值。
对于自动变量,除非显示的初始化,否则不会自动初始化。
寄存器变量:通过register声明,放在寄存器而非内存中,所以无法获得其地址。注意,有时候虽然请求了把变量放于寄存器中,但是由于寄存器的个数,并不一定能满足,所以寄存器变量就成为了普通变量,不过依然不能对其取址。
具有代码块作用域的静态变量:通过static修饰,如果不现实对其初始化,则自动初始化为0(提供了静态时期),在代码块内声明(提供了代码块作用域和空链接),创建具有代码块作用域兼具静态存储的局部变量。这些变量具有代码块作用域,空链接,静态存储时期。变量一旦被定义便一直存在直到程序结束。
具有外部链接的静态变量:具有文件作用域、外部链接、静态时期(外部存储类,外部变量)。 额外说明,外部变量只可进行一次初始化,而且在定义的时候。
具有内部链接的静态变量:用static在所有函数的外部进行定义来创建,静态存储时期、文件作用域、内部链接。
二、存储类说明符 auto 标明一个变量具有自动存储时期,该说明符只能用在具有代码块作用域的变量声明中。
register 只能用在具有代码块作用域的变量。请求一个变量存储在寄存器中一边快速使用,但是不能获得改变量的地址。
static 用于具有代码块作用域的变量声明时,使该变量具有静态存储时期,从而得以在程序运行期间存在并保留其值,变量仍保留代码块作用域和空链接。若用于具有文件作用域的变量声明时,使该变量具有内部链接。
extern 表明在声明一个在其他地方定义了的变量。
const 将数据定为不变的,在只能定义声明,以后不可改变其值。在指针中使用时,const的位置决定是指针本身不变还是指针指向的数据不变。
volatile 说明数据除了可以被程序修改外还可以被其他代理修改,主要用于编译器优化。
restrict 只能用于修饰指针,限定的指针被认为是提供了对其所在指向的数据块的唯一访问途径。
__blocks说明符类似于static、auto、register,它们用于指定将变量值设置到哪个存储域中。例如auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中。
按照之前提供的__block说明符的代码:
#include <stdio.h>
int main(int argc, char * argv[]) {
__block int val = 10;
void (^blk)(void)=^{val=1;};
}
复制代码
转换后的代码:
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(int argc, char * argv[]) {
__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));
}
复制代码
其中
__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结构体的一个实例。该结构体声明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
复制代码
在main函数中给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的__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_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结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。
__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_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。(成员变量val是该实例自身持有的变量,它相当于原来的自动变量)。
另外,__block变量的__Block_byref_val_0结构体并不在Block用__main_block_impl_0结构体中,这样做是为了在多个Block中使用__block变量。
int main(int argc, char * argv[]) {
__block int val = 10;
void (^blk0)(void)=^{val=0;};
void (^blk1)(void)=^{val=1;};
}
复制代码
转换后的代码:
int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
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));
}
复制代码
两个Block都是用了__Block_byref_val_0结构体实例val的指针。这样,就可以从多个Block中使用同一个__block变量。也可以从一个Block中使用多个__block变量,只要增加Block结构体成员变量与构造函数的参数,便可对应使用多个__block变量。
Block存储域
Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的示例。
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实例 |
__block变量 | 栈上__block变量的结构体实例 |
另外,通过以下转换后的代码可知,Block也是Objective-C对象。将Block当作OC对象来看时,Block的类为_NSConcreteStackBlock。
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;
}
};
复制代码
类似的类有:
- _NSConcreteGlobalBlock
- _NSConcreteStackBlock
- _NSConcreteMallocBlock
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域(.data区) |
_NSConcreteMallocBlock | 堆 |
一个由C/C++编译的程序占用的内存分为以下几个部分 :
- 栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
- 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
- 文字常量区:常量字符串就是放在这里的,程序结束后由系统释放
- 程序代码区—存放函数体的二进制代码。
记述在全局变量的地方使用Block语法时,生成的Block为_NSConcreteGlobalBlock类对象。
例如:
void (^blk)(void) = ^{printf("Global Block\n");};
int main(int argc, char * argv[]) {
}
复制代码
转换后:
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
该Block的类为_NSConcreteGlobalStack类。此Block即该Block用结构体实例设置在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。由此Block用结构体实例的内容不依赖于执行时的状态,所以整个程序中需要一个实例。因此将Block用结构体实例设置在与全局变量相同的数据区域中即可。
只在截获自动变量时,Block用结构体实例截获的值才会根据执行时的状态变化。
例如,以下代码中,虽然多次使用一个Block语法,但是每个循环中截获的自动变量的值都不同。
int main(int argc, char * argv[]) {
typedef int (^blk_t)(int);
for (int rate = 0;rate<10;++rate){
blk_t blk = ^(int count){
return rate*count;
};
}
}
复制代码
转换后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int rate;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _rate, int flags=0) : rate(_rate) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int count) {
int rate = __cself->rate; // bound by copy
return rate*count;
}
int main(int argc, char * argv[]) {
typedef int (*blk_t)(int);
for (int rate = 0;rate<10;++rate){
blk_t blk = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, rate));
}
}
复制代码
而以下代码中在不截获自动变量时,Block用结构体实例每次截获的值都完全相同。
int main(int argc, char * argv[]) {
typedef int (^blk_t)(int);
for (int rate = 0;rate<10;++rate){
blk_t blk = ^(int count){
return count;
};
}
}
复制代码
转换后:
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 int __main_block_func_0(struct __main_block_impl_0 *__cself, int count) {
return count;
}
int main(int argc, char * argv[]) {
typedef int (*blk_t)(int);
for (int rate = 0;rate<10;++rate){
blk_t blk = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}
}
复制代码
也就是说,即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
虽然通过clang转换的代码是_NSConcreteStackBlock类对象,但是实现上却不同。
- 记述全局变量的地方有Block语法时
- Block语法的表达式中不使用应截获的自动变量时
以上情况,Block为_NSConcreteGlobalBlock类对象。即Block配置在程序的数据区域中。除此之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。
而_NSConcreteMallocBlock类主要解决以下两个问题
- Block超出变量作用域可存在的理由
- __block变量在结构体成员变量__forwarding存在的理由
配置在全局变量上的Block,从变量作用域外也可以通过指针安全地使用,但设置在栈上的Block,如果气所属的变量作用域结束,该Block就废弃。由于__block变量也配置在栈上,所以它所属的变量作用域结束,则该__block变量也会被废弃。
Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。
复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa。
impl.isa = &_NSConcreteMallocBlock;
复制代码
而__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确地访问__block变量.
Blocks提供的复制方法:当ARC有效时,大多数情形下编译器会恰当地进行判断,自动生成将Block从栈上复制到堆上的代码。
typedef int (^blk_t)(int);
blk_t func(int rate){
return ^(int count){
return rate * count ;
};
};
int main(int argc, char * argv[]) {
}
复制代码
转换时报错
/var/folders/6_/v3tcjsp9277_82bgpvhr8l6c0000gn/T/main-29bba4.mi:441:12: error:
returning block that lives on the local stack
复制代码
原因为该源代码返回配置在栈上的Block 函数,即程序执行中从该函数返回函数调用方时变量作用域结束,因此栈上的Block也被废弃。
但是该源代码通过对应的ARC的编译器可转换如下:
blk_t func(int rate)
{
blk_t temp = &__func_block_impl_0(__func_block_func_0,&__func_block_desc_0_DATA,rate);
temp = objc_retainBlock(temp);
return objc_autoreleaseReturnValue(temp);
}
复制代码
通过objc4运行时库的runtime/objc-arr.mm可知,objc_retainBlock函数实际上就是_Block_copy函数,即:
/*
* 将通过Block语法生成的Block,即配置在栈上的Block用结构体实例赋值给相当于Block类型的变量temp中。
*/
temp = _Block_copy(temp);
/*
* _Block_copy函数将栈上的Block复制到堆上。
* 复制后,将对上的地址作为指针复制给变量temp。
*/
return objc_autoreleaseReturnValue(temp);
/*
* 将堆上的Block作为OC对象注册到autoreleasepool中,然后返回该对象。
*/
复制代码
大多数情况下编译器会适当地进行判断,将Block从栈上复制到堆上。那么不能进行判断的有以下几种情况:
- 向方法或函数的参数中传递Block时
但是如果在方法或函数中适当地复制了传递过来的参数,那么就不必在调用该方法或函数前手动复制了。以下方法或函数不用手动复制:
- Cocoa框架的方法且方法名中含有usingBlock等时
- Grand Central Dispatch的API
举个例子,在使用NSArray类的enumrateObjectsUsingBlock实例方法以及dispatch_async函数时,不用手动复制。相反地,在NSArray类的initWithObjects实例方法上传递Block时需要手动复制。
- (void)viewDidLoad {
[super viewDidLoad];
id obj = [self getBlockArray];
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
}
-(id) getBlockArray{
int val = 10;
return [NSArray arrayWithObjects:^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk1:%d",val);}, nil];
}
复制代码
运行这段代码发现异常,这是由于getBlockArray执行结束时,栈上的Block被废弃了。修改getBlockArray方法如下即可,但是将Block从栈上复制到堆上是相当消耗CPU资源的。
-(id) getBlockArray{
int val = 10;
return [NSArray arrayWithObjects:[^{NSLog(@"blk0:%d",val);} copy],[^{NSLog(@"blk1:%d",val);} copy], nil];
}
复制代码
Block的类 | 副本资源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
由上表可知,不管Block配置在何处,用copy方法复制都不会引起任何问题,在不确定是调用copy方法即可。
但是在ARC中不能显示地release,调用多次copy完全没有问题,解释如下:
blk = [[[[blk copy] copy] copy] copy];
复制代码
解释如下:
{
/** 将配置在栈上的Block赋值给变量blk中。*/
blk_t temp = [blk copy];
/** 将配置在堆上的Block复制给变量temp中,变量temp持用强引用的Block。*/
blk = temp;
/*
* 将变量temp的Block赋值给变量blk,变量blk持有强引用的Block。
* 因为原先赋值的Block配置在栈上,所以不受此复制的影响。
* 此时Block的持有者为变量blk和变量temp。
*/
}
/*
* 由于变量作用域结束,所以变量temp被废弃,其强引用失效并释放所持有的Block
* 由于Block被变量blk持有,所以没有被释放。
*/
{
/*
* 配置在堆上的Block被赋值变量blk,同时变量blk持有强制引用的Block
*/
blk_t temp = [blk copy];
/*
* 配置在堆上的Block被赋值到变量temp中,变量temp持有强引用的Block。
*/
blk = temp;
/*
* 由于向变量blk进行了赋值,所以第一次赋值的Block的强引用失效,Block被释放。
* 由于Block被变量temp所持有,所以没有被废弃。
* 变量blk中赋值了变量temp的Block,变量blk持有了Block的强引用。
* 此时Block的持有者为变量blk和变量temp。
*/
}
/*
* 由于变量作用域结束,所以变量temp被废弃,其强引用失效并释放所持有的Block
* 由于Block被变量blk持有,所以没有被释放。
*/
/*
* 下面重复此过程
*/
复制代码
__block 变量存储域
使用__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变量的引用计数。
如果配置在堆上的Block被废弃,那么它所使用的__block变量也就被释放。
使用__block变量的Block持有__block变量。如果Block被废弃,它所持有的__block变量也就被释放。这与OC的引用计数式内存管理的方式完全相同。
__block变量用结构体成员变量__forwarding,不管__block变量配置在栈上还是堆上,都能够正确地访问该变量。
__block int val =0;
void (^blk)(void)=[^{++val;} copy];
++val;
blk();
NSLog(@"%d",val);
复制代码
执行结果为:
2017-12-13 20:33:37.791190+0800 ImageOrientation[6953:2428079] 2
复制代码
在变换Block语法的函数中,该变量val为复制到堆上的__block变量用结构体实体,而使用的与Block无法的变量val,为复制前栈上的__block变量用结构体实例。
但是栈上的__block变量用结构体实例在__block变量从栈复制到堆上式,会将成员变量__forwarding的值替换为复制目标堆上的__block变量用结构体实例的地址。通过该功能,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上还是堆上,都可以顺利地访问同一个__block变量。