一、代码
说明:本文章须结合文章《block本质探寻一之内存结构》和《class和object_getClass方法区别》加以理解;
//main.m
#import <Foundation/Foundation.h> int a = 10; static int b = 10; int main(int argc, const char * argv[]) { @autoreleasepool { auto int c = 20; static int d = 20; void (^block)(void) = ^{ NSLog(@"a=%d, b=%d, c=%d, d=%d", a, b, c, d); }; a = 30; b = 35; c = 40; d = 45; block(); } return 0; }
//打印
2019-01-09 15:42:16.246684+0800 MJ_TEST[4180:224738] a=30, b=35, c=20, d=45 Program ended with exit code: 0
分析:很显然,只有c的值没有改变,其它变量的值都改变了——为什么,看下底层代码实现;
二、main.cpp
int a = 10; static int b = 10; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int c; int *d; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int c = __cself->c; // bound by copy int *d = __cself->d; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_1f1f41_mi_0, a, b, c, (*d)); } 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, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; auto int c = 20; static int d = 20; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &d)); a = 30; b = 35; c = 40; d = 45; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
分析:
1)C语言语法
<1>int c被转换成auto int c:我们知道c、d为局部变量,而a、b为全局变量,C语言中,所有没有修饰符的局部变量默认的修饰符为auto,static修饰的变量为静态变量,还有一个register注册类的在此不再赘述(自己有兴趣上网查下);
<2>auto类型的局部变量的生命周期为离其最近的大括号内,超出该大括号,该变量被自动销毁;
<3>static类型的变量(不论是全局还是局部),其值一直保留在内存中,不受大括号的限制,程序结束时才被销毁;
2)变量捕获概念
我们发现在block结构体中,存在c、d而不存在a、b变量,在此,我们把存在于block结构体中的外部变量称为变量捕获(存在的形式在所不问),不存在的则没有被捕获,所以a、b变量没有被block捕获;
3)变量调用流程
<1>在block结构体中,c保持不变依然为int型变量,而d被转换成int型指针变量,因此在main函数中通过__main_block_impl_0方法传递实参c本身的值和d指向的内存的值&d;
而在block的构造函数中,c(_c), d(_d)为C++语法<=>c = _c,d = _d,那么main函数中的实参c、&d最终传递给了block结构体中的变量c和指针变量d;
<2>最后在__main_block_func_0方法中,对c、d而言,须先获取block内部的成员变量再输出;而对于a、b,因为是全局变量,所以可以直接引用;
综上所述:
auto局部变量因为作用域(或生命周期)有限,随时会销毁,故block在引用时系统会自动将其值保存在block结构体中(即捕获);而全局变量和static修饰的变量(局部或全局),并不会随时被销毁,其值一直会在内存中保持不变,知道整个程序结束时才销毁
1)另外从另一个角度理解,全局变量其作用域为从其定义的地方开始到该文件结束止都是有效的,所以main函数中可以用,__main_block_func_0函数中也可以用,不需要再将其保存到block自身的结构体中;
2)static修饰的局部变量会被转化成指针变量,而保存到block结构体中也是指针,因为指针本身的值为另一个变量的地址,所以block对该指针的操作始终是对另一个变量的地址的操作,而非另一个变量值的本身,当对d重新赋值时,block中的指针变量指向的变量的值也就随之改变,对*d输出当然被改变(*d即取出指向的内存地址存放的值);
3)auto局部变量被捕获,即是在内存中重新开辟了内存来存放该变量的值(即copy),只不过是在block结构体对应的内存中;
三、结论
1.
2.auto修饰的局部变量在block定义后的修改,不影响block内部对该变量的使用;后两者,有影响;
四、扩展——OC对象捕获问题
1)Person.m——注:此处我将.h文件也贴过来了,为了很好的阅读
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @