Swift闭包

2021-08-29  本文已影响0人  YY323
func test() {
    print("test")
}

👆的全局函数是一种特殊闭包不捕获变量;

👇的内嵌函数也是一个捕获外部变量闭包

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    // 内嵌函数,会捕获外层函数的变量runningTotal
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

闭包表达式:会自动上下文捕获(常)变量

{ (parameters) -> returntype in
    statements
}
  
//将闭包声明为可选类型
var closure : ((Int) -> Int)?
closure = nil

//将闭包表达式赋值给常量
let closure : (Int) -> Int
closure = {(age : Int) -> Int in
    return age
}
//或者省略 -> Int
closure = {(age : Int) in
    return age
}

// 闭包作为函数参数
func test(param : () -> Int) {
    print(param())
}
// 使用尾随闭包
var age = 10
test { ()->Int in
   age += 1
   return age
}

使用闭包表达式好处

  1. 利用上下文推断参数返回值类型
  2. 单一表达式可以隐式返回(省略return关键字)
  3. 简写参数名称($0)
  4. 尾随闭包表达式增加代码的可读性

1.将闭包表达式作为函数的最后一个参数时,如果当前闭包表达式很长,则可以通过尾随闭包的方式来提高代码的可读性

func test(_ a : Int, _ b : Int, _ c : Int, by : (_ item1 : Int, _ item2 : Int, _ item3 : Int) -> Bool) -> Bool {
    return by(a, b, c)
}

// 普通调用:可读性差
test(2, 3, 4, by: {(_ item1 : Int, _ item2 : Int, _ item3 : Int) -> Bool in
    return (item1 + item2 < item3)
}

// 通过尾随闭包调用:简单明了
test(2, 3, 4) {(_ item1 : Int, _ item2 : Int, _ item3 : Int) -> Bool in
    return (item1 + item2 < item3)
}

2.当函数有且仅有闭包表达式作为唯一参数时,可以省略小括号直接写闭包表达式。

应用:简化array.sort的调用语法

var array = [5,1,3]

// 普通调用
array.sort(by: {(item1 : Int, item2 : Int) -> Bool in return item1 < item2 })
// 省略参数类型:闭包表达式可以根据上下文推断参数类型
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
// 闭包表达式作为唯一参数时省略(),直接{}
array.sort {(item1, item2) -> Bool in return item1 < item2 }
// 省略返回值类型:闭包表达式可以根据上下文推断返回值类型
array.sort{(item1, item2) in return item1 < item2 }
// 省略return关键字:单一表达式隐式返回
array.sort{(item1, item2) in item1 < item2 }
// 用$0、$1简写参数
array.sort{$0 < $1 }
// 简化语法终极版
array.sort(by: <)

首先通过之前的例子来分析闭包:

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

通过两种不同的调用方式打印结果:
方式一:

print(makeIncrementer()()) //11
print(makeIncrementer()()) //11
print(makeIncrementer()()) //11

和方式二:

let makeInc = makeIncrementer()
print(makeInc()) //11
print(makeInc()) //12
print(makeInc()) //13

显然上面两种调用方式得到了两种不同的结果:方式一中是每次都重新分配一个变量runningTotal,其值为10,每次调用都是在10的基础上+1,所以得出的结果都是11;而方式二中,调用一次外层函数makeIncrementer就只分配了一个变量runningTotal = 10,函数的返回结果makeInc捕获了变量runningTotal,意味着runningTotal就不再是一个单纯的值10了,而是一个引用地址,所以每次调用就是进行引用类型的赋值操作。

SIL中分析闭包捕获值的本质:

// makeIncrementer()
sil hidden @main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) () -> @owned @callee_guaranteed () -> Int {
bb0:
  // 通过 alloc_box 在堆上分配一块内存,存储metadata、refCount、value,内存地址--->runningTotal
  %0 = alloc_box ${ var Int }, var, name "runningTotal" // users: %8, %7, %6, %1
  // 通过 project_box 取出%0(理解为对象)的value的地址
  %1 = project_box %0 : ${ var Int }, 0           // user: %4
  %2 = integer_literal $Builtin.Int64, 10         // user: %3
  %3 = struct $Int (%2 : $Builtin.Int64)          // user: %4
  // 将10存入%1这个地址里
  store %3 to %1 : $*Int                          // id: %4
  // function_ref incrementer #1 () in makeIncrementer()
  %5 = function_ref @incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %7
  // 闭包调用前对%0(理解为对象)做retain操作
  strong_retain %0 : ${ var Int }                 // id: %6
  // 调用内嵌函数incrementer(闭包)
  %7 = partial_apply [callee_guaranteed] %5(%0) : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %9
   // 闭包调用后对%0(理解为对象)做release操作
  strong_release %0 : ${ var Int }                // id: %8
  return %7 : $@callee_guaranteed () -> Int       // id: %9
} // end sil function 'main.makeIncrementer() -> () -> Swift.Int'
捕获runningTotal的图解

汇编模式下也可看出调用了swift_allocObject

总结:
1.综上可知闭包捕获值的本质就是在上分配一块内存空间存储值的引用
2.闭包能从上下文捕获已被定义的(常)变量,即使它们的原作用域已经不存在,闭包仍能在其函数体内引用修改这些值;
3.每次修改的捕获值其实修改的是堆内存中的value值;
4.每次重新执行当前函数时都会重新创建内存空间。

接下来通过分析IR文件来看一下makeInc这个变量中到底存储的是什么?

<elementnumber> x <elementtype>
iN: N位(bits)的整型
// example 
alloca [24 x i8], align 8  //24个i8(1字节的整型)都是0
// 名称 = type {结构体成员,类型}
%swift.refcounted = type { %swift.type*, i64 } 
//example
%T = type {<type list>} // 类似C的结构体
<type> *
//example
i64*   //8字节的整型指针
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*

通过LLVM官网的例子来熟悉IR语法:

struct munger_struct {
    int f1;
    int f2;
};

void munge(struct munger_struct *P) {
    P[0].f1 = P[1].f1 + P[2].f2;
}

总结:
1.getelementptr后面可以有多层索引,每多一层索引,索引使用的基本类型和返回的指针类型去掉一层;
如上面的i32 0, i32 0:第一层i32 0使用的基本类型是结构体类型(所以相对于自身偏移),返回的是结构体指针类型(拿到的是结构体的地址);第二层i32 0使用的基本类型是int(结构体去掉一层),返回的是*Int(结构体指针去掉一层)。

  1. 第一层索引会改变getelementptr这个表达式的返回类型(因为是相对于自身偏移);

3.第一层索引的偏移量由第一层索引的值和第一层ty指定的基本类型决定的。

P[1].f1 P[2].f2 P[0].f1
  1. 只有一个捕获值时:
    上面例子生成的IR文件中,makeIncrementer函数的实现如下:
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%TSi = type <{ i64 }>

define hidden swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"() #0 {
entry:
  %runningTotal.debug = alloca %TSi*, align 8
  %0 = bitcast %TSi** %runningTotal.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
  %1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #1
  %2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
  %3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
  %4 = bitcast [8 x i8]* %3 to %TSi*
  store %TSi* %4, %TSi** %runningTotal.debug, align 8
  %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  store i64 10, i64* %._value, align 8
  %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
  call void @swift_release(%swift.refcounted* %1) #1
  %6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
  ret { i8*, %swift.refcounted* } %6
}

bitcast: 按位转换,转换前后的类型的位大小必须相同;类似unsafeBitCast
%1: 通过swift_allocObject分配的内存,返回swift.refcounted*类型
%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*: 将swift.refcounted*类型按位转换为{ %swift.refcounted, [8 x i8] }结构体类型
%3: 获取结构体中的[8 x i8]
%4: 将[8 x i8]* 按位转换---> %TSi* (%TSi = type <{ i64 }>)即i64*,存储runningTotal的值
%._value: 偏移后的地址就是%4这个i64*地址,将i64类型的10存储在runningTotal
{ i8*, %swift.refcounted* }: 最终返回的值
i8*--->void *:存储内嵌函数的地址
%swift.refcounted*:存储 %1({ %swift.refcounted, [8 x i8] }>*

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
 
        return runningTotal
    }
    return incrementer
}

/**
 %swift.refcounted = type { %swift.type*, i64 }
 %swift.type = type { i64 }
 %swift.refcounted的数据结构
 */
struct HeapObject {
    var type : UnsafePointer<Int64>
    var refCounts : Int64
}

/**
 bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
 { %swift.refcounted, [8 x i8] }的数据结构
 T : 可传入其它类型
 */
struct BoxValue<T> {
    var heapObj : HeapObject
    var value : T
}

/**
 ret { i8*, %swift.refcounted* } %6
 { i8*, %swift.refcounted* }的数据结构
 */
struct FuncResult<T> {
    var funcAddress : UnsafeRawPointer // 内嵌函数的地址
    var captureValue : UnsafePointer<BoxValue<T>> // 捕获值的地址
}

// 返回类型(包装的结构体,直接传入函数类型的话在转换过程中会出错)
struct Function {
    var function : () -> Int
}

// 将Function<T>绑定到FuncResult<T>的数据结构
var makeInc = Function(function: makeIncrementer())

let funcPtr = UnsafeMutablePointer<Function>.allocate(capacity: 1)
funcPtr.initialize(to: makeInc)

let bindPtr = funcPtr.withMemoryRebound(to: FuncResult<Int>.self, capacity: 1) { p in
    return p.pointee
}

funcPtr.deinitialize(count: 1)
funcPtr.deallocate()

print(bindPtr.funcAddress) //0x0000000100002b80
print(bindPtr.captureValue.pointee.value) //10
通过终端查看内存地址

bindPtr.funcAddress内嵌函数地址对应的符号

  1. 两个捕获值时闭包的数据结构:
func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    var value = 6.00
    func incrementer() -> Int {
        runningTotal += 1
        value += 4.00
        return runningTotal
    }
    return incrementer
}

可知上面的闭包捕获两个值:Int--> runningTotal和 Double--> value,数据结构稍微有一些改变,中间多了一层封装两个捕获值:

/**
<{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>*
%swift.refcounted*: 第一个捕获值的地址 T:第一个捕获值的类型
%swift.refcounted*: 第二个捕获值的地址 V:第二个捕获值的类型
*/
struct MultiBox<T, V> {
    var refCounted : HeapObject
    var value1 :  UnsafePointer<BoxValue<T>>
    var value2 :  UnsafePointer<BoxValue<V>>
}

/**
 ret { i8*, %swift.refcounted* } %6
 { i8*, %swift.refcounted* }的数据结构
 */
struct FuncResult<T, V> {
    var funcAddress : UnsafeRawPointer // 内嵌函数的地址
    var captureValue : UnsafePointer<MultiBox<T, V>> // 捕获值的地址
}

print(bindPtr.funcAddress) //0x0000000100002750
print(bindPtr.captureValue.pointee.value1.pointee.value) //10
print(bindPtr.captureValue.pointee.value2.pointee.value) //6.0

总结:

func makeIncrementer(inc : Int) -> Int {
    var runningTotal = 10
    runningTotal += inc
    return runningTotal
}

var makeInc = makeIncrementer
 
struct FuncData {
    var funcAddress : UnsafeRawPointer
    var captureValue : UnsafeRawPointer?
}

struct Func {
    var function : (Int) -> Int
}

var f = Func(function: makeIncrementer(inc:))
let ptr = UnsafeMutablePointer<Func>.allocate(capacity: 1)
ptr.initialize(to: f)

let funcPtr = ptr.withMemoryRebound(to: FuncData.self, capacity: 1){
    $0.pointee
}

ptr.deinitialize(count: 1)
ptr.deallocate()

print(funcPtr.funcAddress) //0x00000001000030a0
print(funcPtr.captureValue) //nil

/**
cat address 0x0000000100003000
&0x0000000100003000,  FirstSwiftTest.makeIncrementer(inc: Swift.Int) -> Swift.Int <+0> , ($s14FirstSwiftTest15makeIncrementer3incS2i_tF)FirstSwiftTest.__TEXT.__text
*/
1.定义
2. 声明:逃逸闭包前+@escaping
var completionHandler : ((Int)->())?

// 先存储再调用
func makeIncrementer(amount : Int, handler : @escaping (Int) -> ()) {
    var runningTotal = 10
    runningTotal += amount
    // 必须显式引用self,目的是提醒可能有循环引用,自己注意解决
    self.completi
onHandler = handler
}

// 异步延迟调用
func async_makeIncrementer(amount : Int, handler : @escaping (Int) -> ()) {
    var runningTotal = 10
    runningTotal += amount
    
    DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) {
        handler(runningTotal)
    }
}

重点:逃逸闭包的生命周期一定要比函数的生命周期

3.区别非逃逸闭包
func debugErrorMsg(_ condition : Bool, _ message : @autoclosure () -> String) {
    if condition {
        print(message())
    }
}

debugErrorMsg(true, "NetWork Error Occured")

上面例子中,实际就是将传入的字符串"NetWork Error Occured"放入到一个闭包表达式中,在调用的时候返回。即:

{
  return "NetWork Error Occured"
}
上一篇 下一篇

猜你喜欢

热点阅读