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


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)也不会运行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容