Swift

Swift -07:闭包的使用和底层实现

2020-12-25  本文已影响0人  MonKey_Money

1.闭包的概念

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

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

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

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

上⾯的 incrementer 我们称之为内嵌函数,同时从上层函数 makeIncrementer 中捕获变量 runningTotal

 {
 (age: Int) in 
return age 
}

这种就属于我们熟知的闭包表达式,是⼀个匿名函数,⽽且从上下⽂中捕获变量和常量。
其中闭包表达式是 Swift 语法。使⽤闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:
利⽤上下⽂推断参数和返回值类型
单表达式可以隐⼠返回,既省略 return 关键字
参数名称的简写(⽐如我们的 $0)
尾随闭包表达式

1.1闭包表达式

闭包表达式的定义

{
(param)->returnType in
//函数体
}

⾸先按照我们之前的知识积累, OC 中的 Block 其实是⼀个匿名函数,所以这个表达式要具备
作⽤域(也就是⼤括号)
参数和返回值
函数体(in)之后的代码
Swift 中的闭包即可以当做变量,也可以当做参数传递,这⾥我们来看⼀下下⾯的例⼦熟悉⼀下:

var course:(Int)->Int = {
(age:Int) in 
return age
}

同样我们也可以把我们的闭包声明成可选类型

//错误的写法 ,这种声明方式是闭包内的返回值是可选类型并不是闭包为可选类型
var closure:(Int)->Int?
//正确的声明方式
var closure1:((Int)->Int)?
closure1 = nil

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

let closure :(Int)->Int
closure = {
(temp:Int) in
return temp
}

也可以作为函数的参数

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

1.2 尾随闭包

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

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

test(10, 20, 30,by: { (item1, item2, item3) -> Bool in
    
    return (item1+item2 < item3)
})

如果上⾯的参数再⻓⼀点,这⾥我们看⼀个函数调⽤是不是就⾮常费劲,特别是在代码量多的时候

test(10, 20, 30) { (item1, item2, item3) -> Bool in
    
    return (item1+item2 < item3)
}

这⾥⼀眼看上去就在知道⼀个函数调⽤,后⾯是⼀个闭包表达式。⼤家看这个写法,当前闭包表达式{} 放 在了函数外⾯
其实我们刚才在上⾯讲的 array.sorted 是不是其实就是⼀个尾随闭包啊,⽽且是这个函数就只有⼀个 参数。

var array:[Int] = [1,3,2]
array.sort(by: {(item:Int,item2:Int)->Bool in
    return item < item2
})
//省略参数的类型
array.sort(by: {(item,item2)->Bool in
    return item < item2
})
//省略返回值的类型
array.sort(by: {(item,item2) in
    return item < item2
})
array.sort { (item, item2) -> Bool in
    return item < item2
}
//省略参数名,使用系统生成的默认参数
array.sort {return $0<$1}
//省略return 
array.sort {$0<$1}
array.sort (by: <)

2.闭包底层

2.1.通过ir看底层实现

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

通过下面命令看ir : swiftc -emit-ir main.swift | xcrun swift-detangle >> main.il


image.png

我们上面我们可以得出结构体

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: UnsafePointer<T>
}

从1中我们得出FuntionData,从2中我们得出Box<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())
var makeInc2 = 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)

打印结果是

0x0000000100001c70
0x000000000000000c

2.2.捕获两个值

func makeIncrementer(amount:Int) -> () -> Int {
    var runningTotal = 12
    func incrementer() -> Int {
        print(amount)
        runningTotal += 1

        return runningTotal
    }
    return incrementer
}
struct Box<T> {
    var refCounted: HeapObject
    var value: UnsafePointer<T>
    var value2: UnsafePointer<T>
}
var makeInc = VoidIntFun(f: makeIncrementer(amount: 10))
print(ctx.captureValue.pointee)

打印结果

Box<Int>(refCounted: LGSwiftTest.HeapObject(type: 0x0000000100008090, refCount1: 3, refCount2: 2), value: 0x000000000000000a, value2: 0x0000000100743f60)

value的值为10,就是我们传的amount的值,我们查看value2内存情况

(lldb) x/8g 0x0000000100743f60
0x100743f60: 0x0000000100008068 0x0000000000000003
0x100743f70: 0x000000000000000c 0x00007fff3c4c1765
0x100743f80: 0x0000000100008090 0x0000000200000003
0x100743f90: 0x000000000000000a 0x0000000100743f60

可以看出value2是heapObject实例,0x000000000000000c的值为12.
总结
如果捕获的变量是var类型,在编译阶段会被初始化为heapObject,我们可以在闭包内修改变量,并且会影响闭包外的内容。因为变量的地址闭包内知道。

2.3.逃逸闭包

逃逸闭包的定义:当闭包作为⼀个实际参数传递给⼀个函数的时候,并且是在函数返回之后调⽤,我们就 说这个闭包逃逸了。当我们声明⼀个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。
Swift 3.0 之后,系统默认闭包参数是被 @nonescaping
在函数返回之后调⽤1.延迟调用,2,作为属性存储,在后面进行调用

class PWTeacher {
    var complete:((Int)->())?
    var afterDoHander:(()->Void)?
    //作为属性存储,在doComplete调用
    func makeIncrementer(amount:Int,handle:@escaping(Int)->())  {
        var runningTotal = 0
        runningTotal += amount
        self.complete = handle
    }
    func doComplete()  {
        self.complete?(100)
    }
    func doSomeAfter(handle:@escaping ()->Void)  {
        //延迟调用
        DispatchQueue.global().asyncAfter(deadline:.now()+0.1 ) {
            handle()
        }
    }
}

2.4⾃动闭包

1.我们根据条件打印日志

func debugOutPrint(_ conditon:Bool,_ message:String)  {
    if conditon {
       print(" debugPrint \(message)")
    }
}
debugOutPrint(true,"网络错误")

2.但是如果我们的message是其他函数返回来的呢

func errrorString() -> String {
    sleep(5)
    return "网络问题"
}
debugOutPrint(true, errrorString())

如果conditon为false,errrorString也被执行了,很显然是对资源的浪费,我们想到了闭包

func debugOutPrint(_ conditon:Bool,_ message:()->String)  {
    if conditon {
       print(" debugPrint \(message())")
    }
}
debugOutPrint(true, errrorString)

但是这么修改之后我们无法传给string了,如果errrorString有参数那怎么办呢?
3.⾃动闭包

func debugOutPrint(_ conditon:Bool,_ message:@autoclosure ()->String)  {
    if conditon {
       print(" debugPrint \(message())")
    }
}
func errrorString(time:UInt32) -> String {
    print("走到了吗")
    sleep(time)
    return "网络问题"
}
debugOutPrint(true, errrorString(time: 2))
debugOutPrint(true, "网络错误")

当debugOutPrint为false时,我们的errrorString(time: 2)也不会运行。

上一篇 下一篇

猜你喜欢

热点阅读