swift--闭包

闭包

闭包是⼀个捕获了上下⽂的常量或者是变量的函数。

func test(){ 
 print("test") 3
 }

上⾯的函数是⼀个全局函数,也是⼀种特殊的闭包,只不过当前的全局函数并不捕获值。

{ (param) -> ReturnType in
    //方法体
    return ReturnValue
}

这种就属于我们熟知的闭包表达式,是⼀个匿名函数,⽽且从上下⽂中捕获变量和常量。

闭包可以声明⼀个可选类型

//错误的写法,相当于返回值是可选类型
var closure : (Int) -> Int?
closure = nil
//正确的写法
var closure : ((Int) -> Int)?
closure = nil

还可以通过 let 关键字将闭包声明位⼀个常量(也就意味着⼀旦赋值之后就不能改变了)

let closure: (Int) -> Int
closure = {(age: Int) in
    return age
}
closure = {(age: Int) in//报错
    return age
}

同时也可以作为函数的参数

 func test(param : () -> Int){
    print(param())
}
var age = 10
test { () -> Int in
    age += 1
    return age
}

尾随闭包

当我们把闭包表达式作为函数的最后⼀个参数,如果当前的闭包表达式很⻓,我们可以通过尾随闭包的书写⽅式来提⾼代码的可读性

func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item: Int, _ item3: Int) -> Bool) -> Bool{
    return by(a, b, c)
}
//正常写法
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
    return (item1 + item2 < item3)
})
//尾随闭包
test(10, 20, 30) {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
    return (item1 + item2 < item3)
}

闭包表达式的好处

var array = [1, 2, 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(by: {(item1, item2) in return item1 < item2 })
  • 尾随闭包表达式
array.sort{(item1, item2) in return item1 < item2 }
  • 单表达式可以隐⼠返回,既省略 return 关键字
array.sort{(item1, item2) in item1 < item2 }
  • 参数名称的简写(⽐如我们的 $0)
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
array.sort(by: <)

捕获值

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
let makeInc = makeIncrementer()
print(makeInc())
print(makeInc())
print(makeInc())
···············
11
12
13

查看sil文件


// makeIncrementer()
sil hidden @$s4main15makeIncrementerSiycyF : $@convention(thin) () -> @owned @callee_guaranteed () -> Int {
bb0:
  %0 = alloc_box ${ var Int }, var, name "runningTotal" // users: %8, %7, %6, %1
  %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
  store %3 to %1 : $*Int                          // id: %4
  // function_ref incrementer #1 () in makeIncrementer()
  %5 = function_ref @$s4main15makeIncrementerSiycyF11incrementerL_SiyF : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %7
  strong_retain %0 : ${ var Int }                 // id: %6
  %7 = partial_apply [callee_guaranteed] %5(%0) : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %9
  strong_release %0 : ${ var Int }                // id: %8
  return %7 : $@callee_guaranteed () -> Int       // id: %9
} // end sil function '$s4main15makeIncrementerSiycyF'

runningTotal创建在堆上,在闭包执行结束后才会释放。
再通过IR来查看

IR

IR基本语法

  • 数组
//[数量 x 类型]
[ x ] 
 //example 
//iN:N位的整形
 alloca [24 x i8], align 8 24个i8都是0
  • 结构体
%swift.refcounted = type { %swift.type*, i64 } 
 //表示形式 
 %T = type {} //这种和C语⾔的结构体类似
  • 指针类型
 * 
 //example 
 i64* //64位的整形
  • getelementptr 指令
    LLVM中我们获取数组和结构体的成员,通过 getelementptr ,语法规则如下:
 = getelementptr , * {, [inrange]  }* 2  = getelementptr inbounds , * {, [inrange]  }*

通过LLVM的示例来编译成IR代码

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

struct munger_struct array[3];

编译指令查看

clang -S -fobjc-arc -emit-llvm getelementptr.c >./getelementptr.ll && open getelementptr.ll

//结构体声明
%struct.munger_struct = type { i32, i32 }
//
//数组
@array = common global [3 x %struct.munger_struct] zeroinitializer, align 16

; Function Attrs: noinline nounwind optnone ssp uwtable
define void @munge(%struct.munger_struct*) #0 {
//分配一个内存空间存放结构体的地址,所以%2是二级指针
  %2 = alloca %struct.munger_struct*, align 8
  store %struct.munger_struct* %0, %struct.munger_struct** %2, align 8
  %3 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  %4 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
  %5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
  %6 = load i32, i32* %5, align 4
  %7 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  %8 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %7, i64 2
  %9 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %8, i32 0, i32 1
  %10 = load i32, i32* %9, align 4
  %11 = add nsw i32 %6, %10
  %12 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
//取出数组的首地址 偏移0*结构体大小,
  %13 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %12, i64 0
//相对于自己偏移,取出结构体中第一个元素
  %14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0
  store i32 %11, i32* %14, align 4
  ret void
}

int a = array[0]这句对应的LLVM代码应该是这样的:

 a = getelementptr inbounds [4 x i32], [4 x i32]* array, i64 0, i6 4 0

可以看到第⼀个 0 ,这个我们使⽤基本类型 [4 * i32] ,因此返回的指针前进 0 * 16字节,也就是当前 数组⾸地址,第⼆个index , 使⽤的基本类型是 i32 ,返回的指针前进 0字节,也就是当前数组的第⼀个 元素。返回的指针类型为 i32 * .

总结:

  • 第⼀个索引不会改变返回的指针的类型,也就是说ptrval前⾯的*对应什么类型,返回就是什么类型
  • 第⼀个索引的偏移量的是由第⼀个索引的值和第⼀个ty指定的基本类型共同确定的。
  • 后⾯的索引是在数组或者结构体内进⾏索引
  • 每增加⼀个索引,就会使得该索引使⽤的基本类型和返回的指针的类型去掉⼀层

查看闭包IR

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

查看IR


%swift.function = type { i8*, %swift.refcounted* }
//heapObject
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%TSi = type <{ i64 }>

define i32 @main(i32, i8**) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
//接受返回的结构体
  %3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"()
  %4 = extractvalue { i8*, %swift.refcounted* } %3, 0
  %5 = extractvalue { i8*, %swift.refcounted* } %3, 1
  store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 0), align 8
  store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 1), align 8
  ret i32 0
}

define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() #0 {
entry:
//分配一块内存空间
  %0 = 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
  %1 = bitcast %swift.refcounted* %0 to <{ %swift.refcounted, [8 x i8] }>*
  %2 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %1, i32 0, i32 1
  %3 = bitcast [8 x i8]* %2 to %TSi*
  call void asm sideeffect "", "r"(%TSi* %3)
  %._value = getelementptr inbounds %TSi, %TSi* %3, i32 0, i32 0
//将10存入到结构体中
  store i64 10, i64* %._value, align 8
  %4 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %0) #1
  call void @swift_release(%swift.refcounted* %0) #1
//往结构体中插入值,将内嵌函数的地址
  %5 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %0, 1
  ret { i8*, %swift.refcounted* } %5
}

最后方法返回的是一个结构体
翻译成我们常用的代码就是


struct HeapObject{
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}


struct FuntionData{
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer
}

struct Box {
    var refCounted: HeapObject
    var value: T
}

//包装的结构体
struct VoidIntFun {
    var f: () ->Int
}
func makeIncrementer() -> () -> Int {
    var runningTotal = 12
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

var makeInc = VoidIntFun(f: makeIncrementer())


let ptr = UnsafeMutablePointer.allocate(capacity: 1)

ptr.initialize(to: makeInc)

let ctx = ptr.withMemoryRebound(to: FuntionData>.self, capacity: 1) {
    $0.pointee
}

print(ctx.ptr)
print(ctx.captureValue.pointee.value)

总结:

  1. 捕获值的原理: 堆上开辟内存空间,捕获的值放到这个内存空间里面
  2. 修改捕获值的时候:本质是修改堆空间里的值
  3. 闭包是一个引用类型(地址传递), 闭包的底层结构(结构体:函数的地址 + 捕获变量的值) == 闭包

函数也是一个引用类型

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

转成IR

  store i8* bitcast (i64 (i64)* @"$s4main15makeIncrementer3incS2i_tF" to i8*), i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncyS2icvp", i32 0, i32 0), align 8
  store %swift.refcounted* null, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncyS2icvp", i32 0, i32 1), align 8

就是将refcountednull

逃逸闭包

Swift 3.0 之后,系统默认闭包参数是被 @nonescaping ,这⾥我们可以通过 SIL 看出来

// test(by:)
sil hidden @$s4main4test2byyyyXE_tF : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> () {
// %0                                             // users: %2, %1
bb0(%0 : $@noescape @callee_guaranteed () -> ()):
  debug_value %0 : $@noescape @callee_guaranteed () -> (), let, name "by", argno 1 // id: %1
  %2 = apply %0() : $@noescape @callee_guaranteed () -> ()
  %3 = tuple ()                                   // user: %4
  return %3 : $()                                 // id: %4
} // end sil function '$s4main4test2byyyyXE_tF'
  • 非逃逸闭包:
  1. 函数体内执行
  2. 函数执行完毕,闭包消失
  • 逃逸闭包:
  1. 延时调用
class Teacher{
    var complitionHandler: ((Int)->Void)
    
    func makeIncrementer(amount: Int, handler:@escaping (Int) -> Void){
        var runningTotal = 0
        runningTotal += amount
        DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) {
            handler(runningTotal)
        }
    }
}
  1. 存储,后面进行调用
class Teacher{
    var complitionHandler: ((Int)->Void)
    
    func makeIncrementer(amount: Int, handler:@escaping (Int) -> Void){
        var runningTotal = 0
        runningTotal += amount
        self.complitionHandler = handler
    }
}

逃逸闭包的定义:当闭包作为⼀个实际参数传递给⼀个函数的时候,并且是在函数返回之后调⽤,我们就 \说这个闭包逃逸了。当我们声明⼀个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。

如果我们⽤@escaping 修饰闭包之后,我们必须显示的在闭包中使⽤ self。

自动闭包

 func debugOutPrint(_ condition: Bool , _ message: String){
    if condition {
        print("lg_debug:\(message)")
    }
}
debugOutPrint(true, "Application Error Occured")

上述代码会在当前 conditontrue 的时候,打印我们当前的错误信息,也就意味着 false 的时 候当前条件不会执⾏。
如果我们当前的字符串可能是在某个业务逻辑功能中获取的,⽐如下⾯这样写:

func debugOutPrint(_ condition: Bool , _ message: String){
    if condition {
        print("debug:\(message)")
    }
}
func doSomething() -> String{
    print("test")
    return "NetWork Error Occured"
}
debugOutPrint(false, doSomething())

这个时候我们会发现⼀个问题,那就是当前的 conditon,⽆论是 true 还是 false ,当前的⽅法都会执 ⾏。如果当前的 doSomething 是⼀个耗时的任务操作,那么这⾥就造成了⼀定的资源浪费。
这个时候我们想到的是把当前的参数修改成⼀个闭包

func debugOutPrint(_ condition: Bool , _ message:@escaping () -> String){
    if condition {
        message()
    }
}
func doSomething() -> String{
    print("test")
    return "NetWork Error Occured"
}
debugOutPrint(true, doSomething)

这样的话就能够正常在当前条件满⾜的时候调⽤我们当前的 doSomething 的⽅法。
同样的问 题⼜随之⽽来了,那就是这⾥是⼀个闭包,如果我们这个时候就是传⼊⼀个 String 怎么办那?

func debugOutPrint(_ condition: Bool , _ message:@autoclosure () -> String){
    if condition {
        print(message())
    }
}
func doSomething() -> String{
    print("test")
    return "NetWork Error Occured"
}
debugOutPrint(true, doSomething())
debugOutPrint(true, "Application Error Occured")

上⾯我们使⽤ @autoclosure 将当前的表达式声明成了⼀个⾃动闭包,不接收任何参数,返回值是当 前内部表达式的值。所以实际上我们传⼊的 String 就是放⼊到⼀个闭包表达式中,在调⽤的时候返回。

你可能感兴趣的:(swift--闭包)