Swift -07:闭包的使用和底层实现
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
![](https://img.haomeiwen.com/i11530417/efd8e5323b7bee7b.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)也不会运行。