上文中介绍了闭包及block的关系,下面结合具体场景再深入探究。
应用场景
代码如下:
NSInteger globalVar = 1;
static NSInteger globalStaticVar = 1;
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here
NSInteger var = 1;
static NSInteger staticVar = 1;
__block NSInteger blockVar = 1;
NSMutableArray *arr = [NSMutableArray arrayWithArray:@[@1,@2,@3]];
void (^block)(void) = ^(void) {
NSLog(@"局部变量: %ld", var);
NSLog(@"静态变量: %ld", staticVar);
NSLog(@"全局变量: %ld", globalVar);
NSLog(@"全局静态变量: %ld", globalStaticVar);
NSLog(@"block修饰变量: %ld", blockVar);
[arr addObject:@5];
NSLog(@"数组: %@", arr);
globalVar = 3;
};
var = 2;
staticVar = 2;
globalVar = 2;
globalStaticVar = 2;
blockVar = 2;
[arr addObject:@4];
arr = nil;
block();
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
控制台打印
2021-04-21 14:01:55.859015+0800 OCPP[3452:192737] 局部变量: 1
2021-04-21 14:01:55.859754+0800 OCPP[3452:192737] 静态变量: 2
2021-04-21 14:01:55.859880+0800 OCPP[3452:192737] 全局变量: 2
2021-04-21 14:01:55.860001+0800 OCPP[3452:192737] 全局静态变量: 2
2021-04-21 14:01:55.860101+0800 OCPP[3452:192737] block修饰变量: 2
2021-04-21 14:01:55.860419+0800 OCPP[3452:192737] 数组: (
1,
2,
3,
4,
5
)
OC文件底层编译命令: xcrun -sdk iphonesimulator clang -rewrite-objc main.m。生成.cpp 文件
NSInteger globalVar = 1;
static NSInteger globalStaticVar = 1;
struct __Block_byref_blockVar_0 {
void *__isa;
__Block_byref_blockVar_0 *__forwarding;
int __flags;
int __size;
NSInteger blockVar;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSInteger var;
NSInteger *staticVar;
NSMutableArray *arr;
__Block_byref_blockVar_0 *blockVar; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _var, NSInteger *_staticVar, NSMutableArray *_arr, __Block_byref_blockVar_0 *_blockVar, int flags=0) : var(_var), staticVar(_staticVar), arr(_arr), blockVar(_blockVar->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0中__block_impl我们点击去可以看到有个isa指针。所以block本质上就是个对象,一个将定义的函数以及关联的上下文封装起来的一个对象。
我们在block中引用的num,staticNum,blockNum,在这里都被定义成了内部属性.但是并没有发现globalNum和globalStaticNum
num和staticNum还是保留其原来的NSInteger类型,但是blockNum使用的是``__Block_byref_blockNum_0`类型.
结论如下:
变量类型 | block处理方式 |
---|---|
局部变量 | 值捕获,block块外部改变值不会对block内部产生影响 |
静态变量 | 指针捕获,block块外部改变值会对block内部产生影响 |
全局变量/全局静态变量 | 不捕获,直接取值 |
使用__block关键字修饰 | 是指针捕获,但是实现不同于静态变量,它会生成一个新的结构体对象。 |
block形式
block有三种形式,全局block(NSGlobalBlock),栈block(NSStackBlock),堆block(NSMallocBlock)
- 全局block存储在初始化data区
- 栈block存储在栈(stack)区
- 堆block存储在堆(heap)区
NSLog(@"%@", [^(void){
} class]); // 为引用外部变量
NSLog(@"%@", [^(void){
NSInteger num = globalStaticNum;
} class]); // 引用全局静态变量
NSLog(@"%@", [^(void){
NSInteger num = globalNum;
} class]); // 引用全局变量
static NSInteger temp = 1;
NSLog(@"%@", [^(void){
NSInteger num = temp;
} class]); // 静态变量
NSInteger number = 1;
NSLog(@"%@", [^(void){
NSInteger num = number;
} class]); // 局部变量
NSLog(@"%@", [^(void){
NSInteger num = self.num;
} class]); // 局部变量
NSLog(@"%@", [^(void){
NSInteger num = self.numObj;
} class]); // 局部变量
self.block = ^(b1)(void) {
};
NSLog(@"%@",self.block); // copy全局block
self.block = ^(b1)(void) {
id n = self.numObj;
};
NSLog(@"%@",self.block); // copy栈block
/*
控制台打印如下:
__NSGlobalBlock__
__NSGlobalBlock__
__NSGlobalBlock__
__NSGlobalBlock__
__NSStackBlock__
__NSStackBlock__
__NSStackBlock__
__NSGlobalBlock__
__NSMallocBlock__
*/
结论如下:
使用方式 | 结果 |
---|---|
不使用外部变量 | 全局block |
对全局block进行copy操作 | 全局block |
使用外部变量,变量为全局变量 | 全局block |
使用外部变量,变量非全局变量 | 栈block |
对栈block进行copy操作 | 堆block |
对堆block进行copy操作 | 不进行操作,引用计数+1 |
__block关键字
上文提到,使用关键字修饰的变量,在block中进行指针捕获后并没有定义成原数据类型的指针,而是生成了一个新的结构体对象__Block_byref_blockNum_0:
struct __Block_byref_blockVar_0 {
void *__isa;
__Block_byref_blockVar_0 *__forwarding;
int __flags;
int __size;
NSInteger blockVar;
};
- 可以发现__Block_byref_blockNum_0在内部才持有了定义的变量
- 还有一个指向同类型的指针__forwarding
关于forwarding指针:
- forwarding是一个指向自身的指针, 当栈block发生copy操作后,会指向其拷贝的堆block。
- 所以对block中持有的变量修改,其实修改的是最终指向的那个block。这样保证了访问的值是同样了.