#1.block: 从本质上来说,block
是带有自动变量的匿名函数
,是闭包
在 OC 中的实现。
1.1.语法 #block表达式:
^ 返回值类型 (参数列表) {表达式}
#示例1.1:
1 2 3 ^int (int a, int b) { return a * b; };
#block类型变量:
返回值类型 (^变量名)(参数列表) = Block表达式
#示例1.2:
1 2 3 int (^increment)(int ) = ^(int count ) { return count + 1 ; };
#block类型变量作为函数的参数:
1 2 3 4 - (int )blockAsParam:(int (^)(int a,int b))ablock { return ablock (2 ,3 ) ; }
#block类型变量作为返回值:
1 2 3 4 5 - (int (^)(int a,int b))blockAsReturnValue{ return ^int (int x,int y){ return 0 ; }; }
#完整示例1.3:
1 2 3 4 5 @interface UOBlocks : NSObject - (void )callBlocks; - (int )blockAsParam:(int (^)(int a,int b))ablock; - (int (^)(int a,int b))blockAsReturnValue;@end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #import "UOBlocks.h" typedef int (^multiBlock)(int a,int b);@interface UOBlocks ()@property (nonatomic , copy ) multiBlock mMultiBlock;@property (nonatomic , copy ) int (^clickBlock)(int a);@end @implementation UOBlocks - (void )callBlocks { self .mMultiBlock = ^int (int a, int b) { return a * b; }; NSLog (@"++++%d" ,self .mMultiBlock(2 ,3 )); self .clickBlock = ^int (int a) { return a; }; self .clickBlock(5 ); } - (int )blockAsParam:(int (^)(int a,int b))ablock { return ablock(2 ,3 ); } - (int (^)(int a,int b))blockAsReturnValue{ return ^int (int x,int y){ return 0 ; }; }@end
#调用示例:
1 2 3 4 5 UOBlocks *blocks = [UOBlocks new]; [blocks callBlocks]; [blocks blockAsParam:^int(int a, int b) { return a * b; }];
#2.类型
全局的静态 block。不使用外部变量,或者只使用静态变量或全局变量。
保存在栈中的 block。内部引用局部变量或属性,且不能赋值给强引用或copy修饰的变量;
当函数返回时 block 会销毁。
保存在堆中的 block。引用了局部变量或属性,且赋值给强引用或copy修饰的变量。
block 被 copy 时其 isa 指针会被修改为此类型;当引用计数为 0 时 block 会销毁。
三种类型的 block 分别被存储在全局区
、栈区
、堆区
,需要注意的是在 ARC 环境中, block 的类型会与上面的定义有所差异,且往下看~
2.1.MRC Xcode
->Build Phases
->Compile Sources
找到AppDelegate.m
,设置-fno-objc-arc
,禁用其ARC
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 int aGlobalVar = 10 ;@interface AppDelegate ()@property (nonatomic , copy ) void (^aCopyPropertyBlock)();@end - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { static int aStaticVar = 2 ; const int aConstVar = 3 ; void (^aGlobalBlock)() = ^(){ aGlobalVar = 1 ; aStaticVar = 3 ; NSLog (@"%d" ,aConstVar); }; NSLog (@"++aGlobalBlock info:%@" ,aGlobalBlock); int var = 0 ; void (^aStackBlock)() = ^{ NSLog (@"++%d" ,var); }; NSLog (@"++aStackBlock info:%@" ,aStackBlock); void (^aCopyBlock)() = [aGlobalBlock copy ]; NSLog (@"++aCopyBlock info1:%@" ,aCopyBlock); aCopyBlock = [aStackBlock copy ]; NSLog (@"++aCopyBlock info2:%@" ,aCopyBlock); self .aCopyPropertyBlock = ^{ }; NSLog (@"++aCopyPropertyBlock info1:%@" ,self .aCopyPropertyBlock); self .aCopyPropertyBlock = aStackBlock; NSLog (@"++aCopyPropertyBlock info2:%@" ,self .aCopyPropertyBlock); return YES ; }
输出日志:
1 2 3 4 5 6 ++aGlobalBlock info:<__NSGlobalBlock__: 0x10600a8f8 > ++aStackBlock info:<__NSStackBlock__: 0x7ffee9c35d30 > ++aCopyBlock info1:<__NSGlobalBlock__: 0x10600a8f8 > ++aCopyBlock info2:<__NSMallocBlock__: 0x6000020a4ae0 > ++aCopyPropertyBlock info1:<__NSGlobalBlock__: 0x10600a958 > ++aCopyPropertyBlock info2:<__NSMallocBlock__: 0x6000020bc990 >
从示例及日志可以看出:
未访问任何变量、或者只访问了全局变量、静态变量、常量的是 NSGlobalBlock
;
访问了局部变量的是 NSStackBlock
;
对 NSGlobalBlock
的拷贝还是 NSGlobalBlock
;
对 NSStackBlock
的拷贝变成了 NSMallocBlock
;
对于 copy
修饰的 block 属性,将其指向 NSStackBlock
类型的 block2 时,block2 会被拷贝到堆区,变成 NSMallocBlock
。
MRC
环境中 block 的类型与前面讲到的定义相吻合;
另外对于 block 拷贝后得到的结果,其类型是否变化依据源 block 而定,并不是所有的 block 都会被拷贝到堆上
~
2.2.ARC 删除之前在AppDelegate.m
文件后的-fno-objc-arc
,启用ARC
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @interface AppDelegate ()@property (nonatomic , copy ) void (^aCopyPropertyBlock)();@property (nonatomic , strong ) void (^aStrongPropertyBlock)();@end - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { void (^aGlobalBlock)() = ^(){ }; NSLog (@"++aGlobalBlock info:%@" ,aGlobalBlock); int var = 0 ; void (^__weak aStackBlock)(void ) = ^{ NSLog (@"+++%d" ,var); }; NSLog (@"++aStackBlock:%@" ,aStackBlock); void (^aMallocBlock)() = ^{ NSLog (@"++%d" ,var); }; NSLog (@"++aMallocBlock info:%@" ,aMallocBlock); void (^aCopyBlock)() = [aGlobalBlock copy ]; NSLog (@"++aCopyBlock from aGlobalBlock info1:%@" ,aCopyBlock); aCopyBlock = [aStackBlock copy ]; NSLog (@"++aCopyBlock from aStackBlock info2:%@" ,aCopyBlock); self .aCopyPropertyBlock = ^{ }; NSLog (@"++aCopyPropertyBlock from aGlobalBlock info1:%@" ,self .aCopyPropertyBlock); self .aCopyPropertyBlock = aStackBlock; NSLog (@"++aCopyPropertyBlock from aStackBlock info2:%@" ,self .aCopyPropertyBlock); self .aStrongPropertyBlock = ^{ }; NSLog (@"++aStrongPropertyBlock from aGlobalBlock info1:%@" ,self .aStrongPropertyBlock); self .aStrongPropertyBlock = aGlobalBlock; NSLog (@"++aStrongPropertyBlock from aGlobalBlock info2:%@" ,self .aStrongPropertyBlock); self .aStrongPropertyBlock = aStackBlock; NSLog (@"++aStrongPropertyBlock from aStackBlock info3:%@" ,self .aStrongPropertyBlock); return YES ; }
输出日志:
1 2 3 4 5 6 7 8 9 10 ++aGlobalBlock info:<__NSGlobalBlock__: 0x1058440e8> ++aStackBlock:<__NSStackBlock__: 0x7ffeea3bf8c0> ++aMallocBlock info:<__NSMallocBlock__: 0x60000269e070> ++aCopyBlock from aGlobalBlock info1:<__NSGlobalBlock__: 0x1058440e8> ++aCopyBlock from aStackBlock info2:<__NSMallocBlock__: 0x6000026f6490> ++aCopyPropertyBlock from aGlobalBlock info1:<__NSGlobalBlock__: 0x105844128> ++aCopyPropertyBlock from aStackBlock info2:<__NSMallocBlock__: 0x60000269ddd0> ++aStrongPropertyBlock from aGlobalBlock info1:<__NSGlobalBlock__: 0x105844148> ++aStrongPropertyBlock from aGlobalBlock info2:<__NSGlobalBlock__: 0x1058440e8> ++aStrongPropertyBlock from aStackBlock info3:<__NSMallocBlock__: 0x600002699800>
从示例及日志来分析:
未访问任何变量的是 NSGlobalBlock
;
访问了局部变量但赋值给”非”强引用对象的是 NSStackBlock
;
访问了局部变量但赋值给强引用对象的是 NSMallocBlock
;
对 NSGlobalBlock
的拷贝还是 NSGlobalBlock
;
将 NSStackBlock
赋值给copy或strong修饰的block属性时,得到的是 NSMallocBlock
;
ARC
中 block 的类型与前面的定义不再完全相同:
将=
后面引用了局部变量的block,赋值给默认修饰符为strong的aMallocBlock
时,得到的是一个NSMallocBlock
!也就是说ARC
中 原本在栈上
的 block 现在会被自动拷贝到堆上
。
#3.修饰符 block
也是对象,作为属性时其修饰符可以使用copy
和strong
。不过,相信很多人和我一样,从开始接受的教育就是使用copy
。这一章节就来探讨为何这么做,示例分为MRC
和ARC
两种环境。
3.1.MRC #示例3.1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @interface AppDelegate ()@property (nonatomic , retain ) void (^aBlock)();@end @implementation AppDelegate - (void )retBlock{ self .aBlock = ^{ NSLog (@"++++aBlock" ); }; } - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self retBlock]; NSLog (@"++aBlock info1:%@" ,self .aBlock); self .aBlock(); return YES ; }@end
输出日志:
示例为MRC
环境,block 属性aBlock
声明时使用的修饰符为retain
。因为没有引用局部变量,所以此 block 为NSGlobalBlock
类型。运行之后程序正常执行和输出日志,即我们能在定义域外正常访问此NSGlobalBlock
类型的 block 。
#示例3.2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (void )retBlock{ int var = 10 ; self .aBlock = ^{ NSLog (@"++++aBlock:%d" ,var); }; } - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self retBlock]; NSLog (@"++aBlock info1:%@" ,self .aBlock); self .aBlock(); return YES ; }@end
示例对retBlock
方法稍作修改,即aBlock
引用了局部变量,其他代码不变。运行之后程序会在“NSLog(@”++aBlock info1:%@”,self.aBlock);”处崩溃,错误信息为“EXC_BAD_ACCESS (code=1, address=0x28)”,即访问了已经被释放的内存。所以,我们不能在定义域外访问NSStackBlock
类型的 block ,即使将其保存到集合中,在定义域外访问时也会出现野指针问题。
这是因为 block 属性aBlock
访问了局部变量,属于NSStackBlock
,被分配在栈上
。同时其修饰符为retain
,只是引用关系并不改变内存分布。栈上的变量在出了当前定义域之后,随时可能被回收。所以在定义域外访问此 block 时,aBlock
在栈上的内存已经被回收,最终发生崩溃。实际上在声明aBlock
属性时,编译器就已经给出了警示”Retain’ed block property does not copy the block - use copy attribute instead”。
#示例3.3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @interface AppDelegate ()@property (nonatomic , copy ) void (^aBlock)();@end @implementation AppDelegate - (void )retBlock{ int var = 10 ; self .aBlock = ^{ NSLog (@"++++aBlock:%d" ,var); }; } - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self retBlock]; NSLog (@"++aBlock info1:%@" ,self .aBlock); self .aBlock(); return YES ; }@end
示例中属性的修饰符改为copy
,其他代码不变。运行后输出日志:
使用copy
修饰符时,aBlock
类型为NSMallocBlock
,即被拷贝到了堆上
,所以这次访问aBlock
时,没有再出现野指针问题。
3.2.ARC 再来探讨一下ARC
环境中 block 属性的修饰符问题。
#示例3.4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @interface AppDelegate ()@property (nonatomic , strong ) void (^aBlock1)();@property (nonatomic , strong ) void (^aBlock2)();@property (nonatomic , copy ) void (^aBlock3)();@end @implementation AppDelegate - (void )retBlock{ int var = 10 ; self .aBlock1 = ^{ NSLog (@"++++aBlock1" ); }; self .aBlock2 = ^{ NSLog (@"++++aBlock2:%d" ,var); }; self .aBlock3 = ^{ NSLog (@"++++aBlock3:%d" ,var); }; } - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self retBlock]; NSLog (@"++info1:%@" ,self .aBlock1); NSLog (@"++info2:%@" ,self .aBlock2); NSLog (@"++info3:%@" ,self .aBlock3); return YES ; }@end
输出日志:
1 2 3 ++info1:<__NSGlobalBlock__: 0x10faa28f0 > ++info2:<__NSMallocBlock__: 0x6000029edbc0 > ++info3:<__NSMallocBlock__: 0x6000029ed9e0 >
程序正常运行,可以看到ARC
环境下 block 属性不论是否访问了局部变量,在其定义域外都能正常访问和调用。且使用strong
修饰符时 block 的类型也会自动变成NSMallocBlock
,效果与使用copy
时一样。
3.3.小结 综上,block
属性使用copy
修饰符最为稳妥:
1、没有引用局部变量的 block 是NSGlobalBlock
类型,使用retain、strong和copy修饰符时,都能正常在其定义域外访问和调用;
2、引用了局部变量的 block,MRC 中属于NSStackBlock
类型,被保存在栈上
随时可能被回收,使用retain修饰符时,在其定义域外调用会发生野指针问题,除非使用 copy 将其拷贝到堆上
;
3、引用了局部变量的 block,ARC 中赋值给 strong 和 copy 属性时,属于NSMallocBlock
类型,都能在其定义域外正常访问和调用。且 strong 修饰的 block 会自动将源 block 拷贝到了堆上
,效果与 copy 一样。
#4.编译与实现 新建 command line tool 工程,在main.m
文件中写一个 block 的简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main (int argc, const char * argv[]) { @autoreleasepool { int count = 10 ; void (^ blk)(void ) = ^(){ printf ("varable in block:%d" ,count); }; blk (); } return 0 ; }
使用 LLVM 编译器的clang
命令将此段 OC 代码转换成 C++ 的源代码,以查看 block 的具体实现方式:
1 clang -rewrite -objc main.m
编译后会得到一个main.cpp
文件,文件内容比较长,详细内容可到这里 查看 ,这里只看重点的部分:
1 2 3 4 5 6 struct __block_impl { void *isa int Flags int Reserved void *FuncPtr }
这个__block_impl
就是 block 的结构体。block 在编译过程中会被当做结构体进行处理,这里的isa
字段指向 block 所属的类型,前面有具体的介绍;FuncPtr
字段表示函数指针,即对应的是 block 中的{ }
部分,它会指向下面编译器给我们生成的静态函数__main_block_func_0
。
1 2 3 4 static void __main_block_func_0 (struct __main_block_impl_0 *__cself ) { int count = __cself ->count ; printf("varable in block:%d" ,count ); }
这个静态函数__xxx_block_func_0
是一个函数的实现(前缀会根据.m文件的命名而变),对应的是 block 中{ }
的部分。
1 2 3 4 5 6 7 8 9 10 11 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int count; __main_block_impl_0(void * fp , struct __main_block_desc_0 * desc , int _count , int flags =0) : count(_count) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
__main_block_impl_0
是该 block 的具体实现。此结构体中impl
字段是__block_impl
结构体的实例,__main_block_impl_0
是对__block_impl
的进一步封装;count
字段是我们引入的外部变量;Desc
即结构体的描述信息。最后结构体内部还提供了一个初始化函数__main_block_impl_0()
,函数内对相关字段进行了初始化。
1 2 3 4 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)};
这是一个__main_block_desc_0
结构体,用来描述__main_block_impl_0
结构体的信息。其中的Block_size
字段用于记录结构体大小;后面的__main_block_desc_0_DATA
就是新生成的__main_block_desc_0
实例。
1 2 3 4 5 6 7 8 9 int main(int argc, const char * argv[]) { { __AtAutoreleasePool __autoreleasepool; int count = 10 ; void (* blk)(void ) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk) ; } return 0; }
这是main.m
文件中 main 函数编译后的具体内容,看上去有点复杂,下面逐步解析这段内容具体做了什么:
1 __main_block_impl_0 ((void *)__main_block_func_0 , &__main_block_desc_0_DATA , count ))
#1、通过__main_block_impl_0()
构造器创建了一个__main_block_impl_0
结构体的实例。参数__main_block_func_0
正是上面提到的函数实现,对应 block的 { }
里的内容;__main_block_desc_0_DATA
是__main_block_desc_0
结构体的实例,描述__main_block_impl_0
结构体的大小信息;参数count
是我们之前通过int count = 10
声明的局部变量,这里作为值类型传进来。
1 &__main_block_impl_0 ((void *)__main_block_func_0 , &__main_block_desc_0_DATA , count ))
#2、在上一步的基础上加了&
运算符,用来取上面刚创建的__main_block_impl_0
结构体实例的地址。
1 ((void __main_block_func_0, &__main_block_desc_0_DATA, count))
#3、把实例地址转为一个函数地址。
1 void ())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count));
#4、这一整句的作用就是:定义一个函数指针,指向一个新创建的__main_block_impl_0
结构体的实例的地址。
1 ((void )((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
#5、这是 block 的调用blk();
在编译后的结果,通过函数指针 blk 调用函数的实现FnucPtr
。
#5.变量的捕获 5.1.捕获的实现 上面#4章节的示例中,__main_block_impl_0
结构体中impl.isa
指向的是NSConcreteStackBlock
类型。这是因为 main.m 中我们在 block 外声明了一个变量int count = 10
,并且在 block 内部访问了该变量,所以 block 实例被保存在了栈上。
当然,如果我们在 block 内不访问任何局部变量,impl.isa
应该是指向NSConcreteGlobalBlock
类型,但实际上 clang 编译出来的结果却是指向NSConcreteStackBlock
类型:
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main (int argc, const char * argv[]) { @autoreleasepool { ^(void ){ }; } return 0 ; }
编译结果:
1 2 3 4 5 6 7 8 9 10 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; } };
这和 clang 改写的实现及 ARC 有关,关于这个问题可以参考 唐巧 的博客,这里有具体的解释。
比较访问和不访问局部变量的两个版本编译后的main.cpp
文件,注意构造函数__main_block_impl_0()
这部分代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc , int _count, int flags=0 ) : count (_count) { } }; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc , int flags=0 ) { } };
访问局部变量时,构造函数__main_block_impl_0()
的参数里多出一个_count
,这里就是 block 对变量的自动捕获 。在声明 block 时,栈上局部变量count
的值被复制给_count
,继而以参数的形式传入构造函数中,再被复制到__main_block_impl_0
结构体中。
在block中访问的外部变量是复制过去的,写操作不对原变量生效。
5.2.可修改性 捕获后可以修改的变量:
捕获后不可以修改的变量:
#示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int aGloableVal = 1 ;static int aStaticGloable = 2 ;@implementation AppDelegate - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { static int aStaticVal = 3 ; int anInt = 0 ; NSMutableArray *array = [NSMutableArray array]; void (^blockT)(int ) = ^(int blockVal){ aGloableVal = 8 ; aStaticGloable = 9 ; aStaticVal = 10 ; [array addObject:@"2" ]; NSLog (@"局部变量:%d,\n全局变量:%d,\n静态全局变量:%d,\n静态变量:%d,\n数组容量:%lu" , anInt,aGloableVal,aStaticGloable,aStaticVal,(unsigned long )array.count); }; aGloableVal = 4 ; aStaticGloable = 5 ; aStaticVal = 6 ; anInt = 7 ; [array addObject:@"1" ]; blockT(5 ); return YES ; }@end
输出日志:
1 2 3 4 5 局部变量:0, 全局变量:8, 静态全局变量:9, 静态变量:10, 数组容量:2
根据示例,可以得到以下几个信息:
1、全局变量
、静态变量
、静态全局变量
在 block 内可以重新赋值,而局部变量
不行。anInt 在 block 内部重新赋值时,编译器报错“variable is not assignable”。
2、全局变量
、静态变量
、静态全局变量
在初始化时都有默认值,block 内重新赋值后,最终打印出的都是重新修改之后的值。而局部变量
anInt 初始化时 = 0,在调用 block 之前重新赋值 = 1;而 block 内打印的 anInt 的值仍为赋值前的0
。这说明 block 内捕获的局部变量的值是 blok 声明之前的那个值。
3、局部变量
array 赋值时报错“variable is not assignable”,而执行 addObject 操作却不会报错。所以,block 中使用到的局部变量可以执行操作而不能赋值。
4、block 捕获局部变量仅捕获 block 闭包里面会用到的值,其他用不到的变量,它并不会去捕获。
#6.问题来了 为啥全局变量、静态变量在 block 内能修改,而局部变量不能修改?
内存区域分为:堆区、栈区、全局区(数据区)、代码区。
全局变量、静态全局变量都存储在全局区
,所以在 block 内可以直接被修改。
静态变量传递给 block 的是内存地址
值,所以能在 block 里面直接改变值。
局部变量是以值
的方式传递到 block 的构造函数里,block 内不能改变此自动变量的值。
想要在 block 中修改变量的值,有2种方式:
传递内存地址指针到 block 中;
改变存储区方式(如__block anInt);
下面看看通过__block
修饰变量并在 block 内修改变量值的示例:
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main (int argc, const char * argv[]) { @autoreleasepool { __block int count = 10 ; void (^ blk)(void ) = ^(){ count = 11 ; printf ("update varable in block:%d" ,count); }; blk (); } return 0 ; }
通过 clang 编译后得到新的main.cpp
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct __Block_byref_count_0 { void *__isa ;__Block_byref_count_0 *__forwarding ; int __flags ; int __size ; int count ; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 * Desc; __Block_byref_count_0 *count ; __main_block_impl_0 (void *fp, struct __main_block_desc_0 *desc, __Block_byref_count_0 *_count , int flags=0 ) : count (_count ->__forwarding ) { impl.isa = &_NSConcreteStackBlock ; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
可以看到源码中增加了一个__Block_byref_count_0
类型的结构体,内部保存着我们用__block
修饰的变量count
。而__main_block_impl_0()
构造函数中引用的是__Block_byref_count_0
的结构体指针*_count
。这里,结构体指针不能再修改了。但指针指向的内存空间里的值是可以修改的。因此,在 block 内再次对__block
修饰的变量赋值时,实际上是通过修改变量对应的结构体指针内存空间里的 value 来修改变量的值。
相关参考:
#©Apple官方文档 及 #©翻译
#©唐巧
#©思否