翻了很多文章,发现关于Swift闭包关于上下文变量捕获这块,都没有说的很详细,或者Swift2这样的老版本已经不适用了,问了GPT也是和自己实验的结果不一样,记录下来。
一:OC的block
首先,回顾一下OC中的block。
block对局部变量基本数据类型的捕获,是在创建时捕获了值,并保存副本在自己的结构体中,修改也是修改副本,不会影响到原本的值。
例子:
typedef void (^MyBlock)(void);
- (MyBlock)createBlock {
int number = 10;
MyBlock block = ^{
NSLog(@"Captured value: %d", number);
};
number = 20;
return block;
}
- (void)executeBlock {
MyBlock myBlock = [self createBlock];
myBlock();
}
最后输出是:
Captured value: 10
二:__block修饰符
如果希望block内部修改的值是原本的值,或者希望block捕获的值后面还会变化,需要对原本的变量添加__block修饰符。
typedef void (^MyBlock)(void);
- (MyBlock)createBlock {
__block int number = 10;
MyBlock block = ^{
NSLog(@"Captured value: %d", number);
};
number = 20;
return block;
}
- (void)executeBlock {
MyBlock myBlock = [self createBlock];
myBlock();
}
最后输出是:
Captured value: 20
三:Swift闭包
那Swift闭包对局部基本数据类型的变量的捕获是怎样的呢?Swift中没有__block修饰符,是不是就没法做到block那样的功能了呢?
答案是否定的,例子:
var i = 0
let closure = {
print("\(i)")
}
i += 1
print("\(i)")
closure()
print("\(i)")
输出结果是什么呢?
第一个输出是1,很好理解。
第二个输出是闭包里的i,输出多少呢?
第三个输出是1,也好理解。
看答案:
闭包内输出是1 ,这好像和OC中block是不一样的?接着往下看。
顺便,很无奈的是,GPT给的答案也是错误的:
那么,看上去和OC中捕获__block修饰符的int是一样的?继续尝试:
var i = 0
let closure = {
print("\(i)")
i += 1
}
i += 1
print("\(i)")
closure()
print("\(i)")
输出结果是什么呢?
第一个输出是1,很好理解。
第二个输出是闭包里的i,是1,刚才已经看到了。
第三个输出输出多少呢?
看答案:
答案是2。说明闭包内修改的值,也会反应到闭包外部。
GPT给的答案也是错误的:
再看一个例子:
var i = 0
let closure = {
print("\(i)")
}
i += 1
closure()
i += 1
closure()
i += 1
closure()
输出会是什么呢?
所以这几个例子都证明了,闭包对变量进行捕获,是将变量复制到了堆上,之后不论是闭包内,还是闭包外,操作的值,都是堆上的这个值,闭包对这个值强持有。
捕获值的本质是将变量存储到堆上。
1、一个闭包能够从上下文捕获已经定义的常量和变量,并且能够在其函数体内引用和修改这些值,即使这些定义的常量和变量的原作用域不存在;
2、修改捕获值实际是修改堆区中的value值;
3、当每次重新执行当前函数时,都会重新创建内存空间。
四:捕获列表
那么怎么做到,在定义时就确定捕获的值呢?就像block那样,没有__block修饰符的int?
使用捕获列表 [] in
很容易理解,不再赘述。