美文网首页
swift--闭包

swift--闭包

作者: Mjs | 来源:发表于2020-12-25 17:54 被阅读0次

    闭包

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

    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 类型]
    [<elementnumber> x <elementtype>] 
     //example 
    //iN:N位的整形
     alloca [24 x i8], align 8 24个i8都是0
    
    • 结构体
    %swift.refcounted = type { %swift.type*, i64 } 
     //表示形式 
     %T = type {<type list>} //这种和C语⾔的结构体类似
    
    • 指针类型
    <type> * 
     //example 
     i64* //64位的整形
    
    • getelementptr 指令
      LLVM中我们获取数组和结构体的成员,通过 getelementptr ,语法规则如下:
    <result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}* 2 <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;
    }
    
    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<T>{
        var ptr: UnsafeRawPointer
        var captureValue: UnsafePointer<T>
    }
    
    struct Box<T> {
        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<VoidIntFun>.allocate(capacity: 1)
    
    ptr.initialize(to: makeInc)
    
    let ctx = ptr.withMemoryRebound(to: FuntionData<Box<Int>>.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--闭包

          本文链接:https://www.haomeiwen.com/subject/jfvsnktx.html