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 类型]
[<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 就是放⼊到⼀个闭包表达式中,在调⽤的时候返回。

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

推荐阅读更多精彩内容

  • 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代...
    下页天阅读 346评论 0 1
  • 闭包的概念 Swift闭包表达式 使用闭包返回值 使用尾随闭包 捕获上下文中的变量和常量 支持闭包有两个前提1、支...
    优雅的步伐阅读 1,390评论 2 2
  • 1.闭包的概念 闭包是⼀个捕获了上下⽂的常量或者是变量的函数, 上⾯的函数是⼀个全局函数,也是⼀种特殊的闭包,只不...
    MonKey_Money阅读 719评论 0 0
  • swift进阶 学习大纲[https://www.jianshu.com/p/0fc67b373540] 在 sw...
    markhetao阅读 874评论 0 3
  • 闭包(Closure)在本质上等于函数,和oc的block类似闭包是一种函数的简写形式,省去函数名,把参数和返回值...
    XieHenry阅读 824评论 0 0