闭包
闭包是⼀个捕获了上下⽂的常量或者是变量的函数。
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 类型]
[ x ]
//example
//iN:N位的整形
alloca [24 x i8], align 8 24个i8都是0
- 结构体
%swift.refcounted = type { %swift.type*, i64 }
//表示形式
%T = type {} //这种和C语⾔的结构体类似
- 指针类型
*
//example
i64* //64位的整形
-
getelementptr
指令
LLVM中我们获取数组和结构体的成员,通过getelementptr
,语法规则如下:
= getelementptr , * {, [inrange] }* 2 = getelementptr inbounds , * {, [inrange] }*
通过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{
var ptr: UnsafeRawPointer
var captureValue: UnsafePointer
}
struct Box {
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.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let ctx = ptr.withMemoryRebound(to: FuntionData>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.captureValue.pointee.value)
总结:
- 捕获值的原理: 堆上开辟内存空间,捕获的值放到这个内存空间里面
- 修改捕获值的时候:本质是修改堆空间里的值
- 闭包是一个引用类型(地址传递), 闭包的底层结构(结构体:函数的地址 + 捕获变量的值) == 闭包
函数也是一个引用类型
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
就是将refcounted
为null
逃逸闭包
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'
- 非逃逸闭包:
- 函数体内执行
- 函数执行完毕,闭包消失
- 逃逸闭包:
- 延时调用
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)
}
}
}
- 存储,后面进行调用
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")
上述代码会在当前 conditon
为 true
的时候,打印我们当前的错误信息,也就意味着 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 就是放⼊到⼀个闭包表达式中,在调⽤的时候返回。