「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。
- 本文主要介绍Swift中闭包的
捕获。
1. 捕获值
我们在学习闭包捕获值的时候可以回顾下OC中block捕获值的情况
- (void)testBlock{
NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld:", i)
};
i += 1;
NSLog(@"before block %ld:", i);
block();
NSLog(@"after block %ld:", i);
}
打印的是结果是2,1,2。说明编译的时候已经捕获了i=1的情况,此时如果我们改变i也不会对捕获的变量产生影响,类似值拷贝。那么如果我们想要外部的修改能够影响当前 block 内部捕获的值,我们只需要对当前的 i 添加__block修饰符
- (void)testBlock{
__block NSInteger i = 1;
void(^block)(void) = ^{
i += 1;
NSLog(@"block %ld:", i)
};
i += 1;
NSLog(@"before block %ld:", i);
block();
NSLog(@"after block %ld:", i);
}
这里相当于指针拷贝,捕获的变量可以指向外部i的内存空间,相当于对同一片内存空间进行操作.
- 那么我们在Swift中写类似的代码
var i = 1
let closure = {
print("closure:\(i)")
}
i += 1
print("before closure:\(i)")
closure()
print("after closure:\(i)")
打印的结果
说明和我们的block有一定区别,这里直接使用外部的变量i。我们编译下查看sil文件
可以发现当前的i并不是自己变量,而是拿到全局变量的地址,之后取出里面的值进行打印。
另外我们知道 OC中block有全局block,栈block,堆block,但是在Swift的闭包没有这个概念。我们打印不管是全局变量还是局部变量,存储的都是metadata。
2. 探究捕获值
我们定义一个一个函数,里面有个闭包
可以看出,打印的结果每次都是在上次函数执行的基础上累加的,但是我们所知的runningTotal是一个临时变量,按理说每次进入函数都是10,这里为什么会每次累加呢?
我们可以类似于类来理解,上面的函数中的闭包捕获了变量runningTotal,之后再次调用函数的时候相当于对捕获的变量进行操作。
当我们重新定义这个函数调用的时候,就相当于重新调用。
2.1 SIL分析
我们看下makeIncrementer的实现,这里可以发现alloc_box,我们查看官方文档说明:在堆区创建一个足够大的空间容纳T类型。而project_box,则是把创建的T类型的对象放到盒子中。因此我们上面的闭包相当于把runningTotal拷贝到堆区,它就变成了实例对象的一部分。 因此我们定义的makeInc就是同一实例对象的堆空间地址,而我们makeIncrementer()每次创建都是一个新的实例对象堆空间地址。
2.2 小结
- 一个闭包能够从上下文捕获已经定义的常量和变量,即使这些定义的常量和变量的原作用域不存在,闭包仍然能够在其函数体内引用和修改这些值
- 当每次
修改捕获值时,修改的是堆区中的value值 - 当每次
重新执行当前函数时,都会重新创建内存空间
3. OC Block 和 Swift闭包相互调用
我们在OC中定义的Block,在Swift中是如何调用的那我们来看一下
typedef void(^ResultBlock)(NSString *message);
@interface LGTest : NSObject
+ (void)testBlockCall:(ResultBlock)block;
@end
//实现
+(void)testBlockCall:(ResultBlock)block
{
if (block) {
block(@"hello");
}
在 Swift 中我们可以这么使用
LGTest.testBlockCall { message in
let errorcast = message as String
print(errorcast)
}
我们也可以自己回调使用这个oc的Block
func testBlock(_ block :ResultBlock){
let error = "not Found"
block(error)
}
testBlock { message in
print(message)
}
我们在 Swift里这么定义,在OC中也是可以使用的
class LGPerson: NSObject {
@objc static var closure: (() -> ())?
}
+(void)testBlockCall:(ResultBlock)block
{
//if (block) {
// block(@"hello");
//}
LGPerson.closure = ^{
NSLog(@"hello closure");
};
LGPerson.closure();
}