「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。
1. defer
关于defer{}的定义:在函数执行完成后或者返回后,会执行defer中的函数,即使抛出异常的情况也会调用。
看个简单的列子:
func f() {
defer { print("First defer") }
defer { print("Second defer") }
print("End of function")
}
f()
如果多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是 先出现的后执行。
执行结果
return的情况
func test(){
defer {
print(#function)
}
guard false else{
return
}
}
test()
执行结果,如果把defer{}放在判断下面,直接return了,就不会执行了
我们对于一些统一的操作可以使用defer{},这样会使代码更加优雅。比如我们知道手动创建指针内存空间的时候,不用的时候要销毁,这个时候就可以使用defer
let count = 1
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)
defer {
pointer.deinitialize(count: count)
pointer.deallocate()
}
或者网络请求的时候,可能有不同的分支进行回调函数的执行
func netRquest(completion: () -> Void) {
defer {
//self.isLoading = false
completion()
}
let requestStatus = false
guard requestStatus == true else {
print("requestStatus 400")
return
}
}
2. 捕获2个变量或多个变量
之前我们分析过了,关于闭包捕获一个变量的情况,那么如果闭包中是对象的话
class Person {
var age = 10
}
func test() {
let p = Person()
let closure = {
p.age += 1
}
closure()
}
test()
我们查看IR的代码
这里我们没有在堆区再次创建实例变量p,而是拿到p的地址存储到闭包中。这个过程不需要捕获实例变量p到堆区了。因此只需要在使用的时候进行引用类型地址的传递即可。
那么多个情况下是什么样的情况
func makeIncrementer() -> (_ amount:Int) -> Int {
var runningTotal = 10
var a = 1
func incrementer(_ amount:Int) -> Int {
runningTotal += 1
a += 1
runningTotal += a
runningTotal += amount
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
print(makeInc(1))
我们添加2个变量,查看IR代码
在捕获多个值后,相对应的也多次调用了
swift_allocObject 方法。第一次和第二次都是存储变量Int类型的值。第三次的时候返回的实例被强制转换成了一个结构体指针:<{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>*,之后通过getelementptr把创建的结构体也就是捕获的变量存储到 %13 这个结构体。
我们用结构体进行还原
func makeIncrementer() -> (_ amount: Int) -> Int{
var runningTotal = 1
var b = 2
//内嵌函数,也是一个闭包
func incrementer(_ amount: Int) -> Int{
runningTotal += amount//21
b += 2;//4
runningTotal += b//25
return runningTotal
}
return incrementer
}
//数据结构 : 闭包的执行地址 + 捕获变量堆空间的地址
struct ClosureData<T>{
var ptr: UnsafeRawPointer
var object: UnsafePointer<T>
}
//捕获值的box
struct TowParamsClosureData<T1, T2>{
var object: HeapObject
var value1: UnsafePointer<Box<T1>> //第一个变量
var value2: UnsafePointer<Box<T2>> //第二个变量
}
struct HeapObject{
var metadata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T>{
var object: HeapObject
var value: T
}
struct NoMeanStruct{
var f: (Int) -> Int
}
var f = NoMeanStruct(f: makeIncrementer())
f.f(20)
let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: ClosureData<TowParamsClosureData<Int, Int>>.self, capacity: 1){
$0.pointee
}
print(ctx.ptr)
print(ctx.capatureValue.pointee.value1.pointee.value)
print(ctx.capatureValue.pointee.value2.pointee.value)
打印结果
当我们捕获3个变量的时候,类似我们在box中添加value3,之后打印
这里的amount并不要捕获,这个相当于实例对象的属性,类似我们block中参数,不会造成引用计数增加。
- 改变参数的位置
我们把
amount的位置改为外部的函数的参数不是闭包的函数
我们再改变结构体类型以及去掉闭包的类型
我们打印捕获的变量runningTotal和传入的参数100.
我们直接打印ctx
3. 总结
对于多个值进行捕获的时候,会把捕获的变量放到一个结构体的boxValues中,之后存储到闭包的结构体中。对于闭包的参数,不会进行捕获,相当于闭包对象的属性。闭包不会捕获堆区的对象,只会引用堆区对象的地址。