-
闭包的定义
闭包是一个捕获
了上下文
的常量或变量的匿名函数
。
func test() {
print("test")
}
👆的全局函数
是一种特殊
的闭包
,不捕获
变量;
👇的内嵌函数
也是一个捕获外部变量
的闭包
:
func makeIncrementer() -> () -> Int {
var runningTotal = 10
// 内嵌函数,会捕获外层函数的变量runningTotal
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
-
闭包的写法
闭包表达式
:会自动
从上下文
中捕获
(常)变量
{ (parameters) -> returntype in
statements
}
//将闭包声明为可选类型
var closure : ((Int) -> Int)?
closure = nil
//将闭包表达式赋值给常量
let closure : (Int) -> Int
closure = {(age : Int) -> Int in
return age
}
//或者省略 -> Int
closure = {(age : Int) in
return age
}
// 闭包作为函数参数
func test(param : () -> Int) {
print(param())
}
// 使用尾随闭包
var age = 10
test { ()->Int in
age += 1
return age
}
使用闭包表达式
的好处
:
- 利用上下文推断
参数
和返回值
类型 - 单一表达式可以
隐式返回
(省略return
关键字) -
简写参数
名称($0) -
尾随闭包表达式
增加代码的可读性
-
尾随闭包
1.将闭包表达式作为函数的最后一个参数
时,如果当前闭包表达式很长
,则可以通过尾随闭包
的方式来提高代码的可读性
。
func test(_ a : Int, _ b : Int, _ c : Int, by : (_ item1 : Int, _ item2 : Int, _ item3 : Int) -> Bool) -> Bool {
return by(a, b, c)
}
// 普通调用:可读性差
test(2, 3, 4, by: {(_ item1 : Int, _ item2 : Int, _ item3 : Int) -> Bool in
return (item1 + item2 < item3)
}
// 通过尾随闭包调用:简单明了
test(2, 3, 4) {(_ item1 : Int, _ item2 : Int, _ item3 : Int) -> Bool in
return (item1 + item2 < item3)
}
2.当函数有且仅有
闭包表达式作为唯一参数
时,可以省略小括号
直接写闭包表达式。
应用:简化array.sort
的调用语法
var array = [5,1,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 {(item1, item2) -> Bool in return item1 < item2 }
// 省略返回值类型:闭包表达式可以根据上下文推断返回值类型
array.sort{(item1, item2) in return item1 < item2 }
// 省略return关键字:单一表达式隐式返回
array.sort{(item1, item2) in item1 < item2 }
// 用$0、$1简写参数
array.sort{$0 < $1 }
// 简化语法终极版
array.sort(by: <)
-
闭包的本质:捕获值
首先通过之前的例子来分析闭包:
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
通过两种不同的调用方式打印结果:
方式一:
print(makeIncrementer()()) //11
print(makeIncrementer()()) //11
print(makeIncrementer()()) //11
和方式二:
let makeInc = makeIncrementer()
print(makeInc()) //11
print(makeInc()) //12
print(makeInc()) //13
显然上面两种调用方式得到了两种不同的结果:方式一中是每次都重新分配
一个变量runningTotal,其值为10,每次调用都是在10的基础上+1,所以得出的结果都是11;而方式二中,调用一次外层函数makeIncrementer就只分配了一个变量runningTotal = 10,函数的返回结果makeInc捕获
了变量runningTotal,意味着runningTotal就不再是一个单纯的值10了,而是一个引用地址
,所以每次调用就是进行引用类型的赋值操作。
SIL中分析闭包捕获值的本质:
// makeIncrementer()
sil hidden @main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) () -> @owned @callee_guaranteed () -> Int {
bb0:
// 通过 alloc_box 在堆上分配一块内存,存储metadata、refCount、value,内存地址--->runningTotal
%0 = alloc_box ${ var Int }, var, name "runningTotal" // users: %8, %7, %6, %1
// 通过 project_box 取出%0(理解为对象)的value的地址
%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
// 将10存入%1这个地址里
store %3 to %1 : $*Int // id: %4
// function_ref incrementer #1 () in makeIncrementer()
%5 = function_ref @incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %7
// 闭包调用前对%0(理解为对象)做retain操作
strong_retain %0 : ${ var Int } // id: %6
// 调用内嵌函数incrementer(闭包)
%7 = partial_apply [callee_guaranteed] %5(%0) : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %9
// 闭包调用后对%0(理解为对象)做release操作
strong_release %0 : ${ var Int } // id: %8
return %7 : $@callee_guaranteed () -> Int // id: %9
} // end sil function 'main.makeIncrementer() -> () -> Swift.Int'
汇编模式
下也可看出调用了swift_allocObject
:
总结:
1.综上可知闭包捕获值的本质
就是在堆
上分配一块内存
空间存储值的引用
;
2.闭包能从上下文捕获已被定义的(常)变量,即使它们的原作用域
已经不存在
,闭包仍能在其函数体内引用
和修改
这些值;
3.每次修改的捕获值
其实修改的是堆内存
中的value
值;
4.每次重新执行
当前函数时都会重新创建
内存空间。
-
闭包是引用类型
接下来通过分析IR
文件来看一下makeInc
这个变量中到底存储的是什么?
-
简单的
IR语法
: - 数组
<elementnumber> x <elementtype>
iN: N位(bits)的整型
// example
alloca [24 x i8], align 8 //24个i8(1字节的整型)都是0
- 结构体
// 名称 = type {结构体成员,类型}
%swift.refcounted = type { %swift.type*, i64 }
//example
%T = type {<type list>} // 类似C的结构体
- 指针
<type> *
//example
i64* //8字节的整型指针
- getelementptr指令
用于LLVM
中获得指向数组的元素
和指向结构体成员
的指针
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}*
<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;
}
%2
: 创建一块内存空间
存放数组
的首地址
(数组中存放[结构体1的地址,结构体2的地址,结构体3的地址]
)--->%struct.munger struct** %2
访问二级指针load %struct.munger struct*, %struct.munger struct**%2, align 8
----->取出二级指针%2
中的地址返回(数组的首地址
)getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
:第一层索引
i64 1,相对于自身
(这里是数组)偏移1个i64大小的地址即arr[1]的地址getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
:第一层索引i32 0相对于自身(使用的基本类型结构体)偏移0个i32大小的地址(还是arr[0]这个结构体的首地址),第二层
i32 0相对于结构体
中的元素偏移0个i32大小的地址(结构体中第一个元素f1的地址 *i32)
总结:
1.getelementptr
后面可以有多层
索引,每多一层索引,索引使用的基本类型
和返回的指针类型
去掉一层;
如上面的i32 0, i32 0:第一层
i32 0使用的基本类型是结构体类型
(所以相对于自身偏移),返回的是结构体指针
类型(拿到的是结构体的地址);第二层
i32 0使用的基本类型是int
(结构体去掉一层),返回的是*Int
(结构体指针去掉一层)。
- 第一层索引
不
会改变getelementptr
这个表达式的返回类型
(因为是相对于自身偏移);
3.第一层索引的偏移量
由第一层索引的值
和第一层ty指定的基本类型
决定的。
-
分析闭包的数据结构
- 只有
一个捕获值
时:
上面例子生成的IR文件中,makeIncrementer
函数的实现如下:
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%TSi = type <{ i64 }>
define hidden swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"() #0 {
entry:
%runningTotal.debug = alloca %TSi*, align 8
%0 = bitcast %TSi** %runningTotal.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
%1 = 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
%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
%3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
%4 = bitcast [8 x i8]* %3 to %TSi*
store %TSi* %4, %TSi** %runningTotal.debug, align 8
%._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
store i64 10, i64* %._value, align 8
%5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
call void @swift_release(%swift.refcounted* %1) #1
%6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
ret { i8*, %swift.refcounted* } %6
}
bitcast
: 按位转换,转换前后的类型的位大小
必须相同
;类似unsafeBitCast
%1
: 通过swift_allocObject
分配的内存,返回swift.refcounted*
类型
%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
: 将swift.refcounted*
类型按位转换为{ %swift.refcounted, [8 x i8] }
结构体类型
%3
: 获取结构体中的[8 x i8]
%4
: 将[8 x i8]*
按位转换---> %TSi* (%TSi = type <{ i64 }>)即i64*
,存储runningTotal
的值
%._value
: 偏移后的地址就是%4这个i64*地址,将i64类型的10
存储在runningTotal
中
{ i8*, %swift.refcounted* }
: 最终返回的值
i8*
--->void *:存储内嵌函数
的地址
%swift.refcounted*
:存储 %1({ %swift.refcounted, [8 x i8] }>*
)
- 模拟闭包的数据结构
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
/**
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%swift.refcounted的数据结构
*/
struct HeapObject {
var type : UnsafePointer<Int64>
var refCounts : Int64
}
/**
bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
{ %swift.refcounted, [8 x i8] }的数据结构
T : 可传入其它类型
*/
struct BoxValue<T> {
var heapObj : HeapObject
var value : T
}
/**
ret { i8*, %swift.refcounted* } %6
{ i8*, %swift.refcounted* }的数据结构
*/
struct FuncResult<T> {
var funcAddress : UnsafeRawPointer // 内嵌函数的地址
var captureValue : UnsafePointer<BoxValue<T>> // 捕获值的地址
}
// 返回类型(包装的结构体,直接传入函数类型的话在转换过程中会出错)
struct Function {
var function : () -> Int
}
// 将Function<T>绑定到FuncResult<T>的数据结构
var makeInc = Function(function: makeIncrementer())
let funcPtr = UnsafeMutablePointer<Function>.allocate(capacity: 1)
funcPtr.initialize(to: makeInc)
let bindPtr = funcPtr.withMemoryRebound(to: FuncResult<Int>.self, capacity: 1) { p in
return p.pointee
}
funcPtr.deinitialize(count: 1)
funcPtr.deallocate()
print(bindPtr.funcAddress) //0x0000000100002b80
print(bindPtr.captureValue.pointee.value) //10
bindPtr.funcAddress
:内嵌函数
的地址
对应的符号
。
-
两个捕获值
时闭包的数据结构:
func makeIncrementer() -> () -> Int {
var runningTotal = 10
var value = 6.00
func incrementer() -> Int {
runningTotal += 1
value += 4.00
return runningTotal
}
return incrementer
}
可知上面的闭包捕获两个值:Int--> runningTotal和 Double--> value,数据结构稍微有一些改变,中间多了一层封装两个捕获值:
/**
<{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>*
%swift.refcounted*: 第一个捕获值的地址 T:第一个捕获值的类型
%swift.refcounted*: 第二个捕获值的地址 V:第二个捕获值的类型
*/
struct MultiBox<T, V> {
var refCounted : HeapObject
var value1 : UnsafePointer<BoxValue<T>>
var value2 : UnsafePointer<BoxValue<V>>
}
/**
ret { i8*, %swift.refcounted* } %6
{ i8*, %swift.refcounted* }的数据结构
*/
struct FuncResult<T, V> {
var funcAddress : UnsafeRawPointer // 内嵌函数的地址
var captureValue : UnsafePointer<MultiBox<T, V>> // 捕获值的地址
}
print(bindPtr.funcAddress) //0x0000000100002750
print(bindPtr.captureValue.pointee.value1.pointee.value) //10
print(bindPtr.captureValue.pointee.value2.pointee.value) //6.0
总结:
- 闭包
捕获值
的原理就是在堆
上开辟内存空间,存储捕获值
; - 修改捕获值时就是去堆上修改
- 闭包是
引用类型
(地址传递),闭包的底层结构-->结构体(函数的地址 + 捕获变量的值)
- 闭包不捕获值时(
函数
)也是一个引用类型
(地址传递),底层结构还是一个结构体(函数地址 + null)
,只保存了函数地址。
func makeIncrementer(inc : Int) -> Int {
var runningTotal = 10
runningTotal += inc
return runningTotal
}
var makeInc = makeIncrementer
struct FuncData {
var funcAddress : UnsafeRawPointer
var captureValue : UnsafeRawPointer?
}
struct Func {
var function : (Int) -> Int
}
var f = Func(function: makeIncrementer(inc:))
let ptr = UnsafeMutablePointer<Func>.allocate(capacity: 1)
ptr.initialize(to: f)
let funcPtr = ptr.withMemoryRebound(to: FuncData.self, capacity: 1){
$0.pointee
}
ptr.deinitialize(count: 1)
ptr.deallocate()
print(funcPtr.funcAddress) //0x00000001000030a0
print(funcPtr.captureValue) //nil
/**
cat address 0x0000000100003000
&0x0000000100003000, FirstSwiftTest.makeIncrementer(inc: Swift.Int) -> Swift.Int <+0> , ($s14FirstSwiftTest15makeIncrementer3incS2i_tF)FirstSwiftTest.__TEXT.__text
*/
-
逃逸闭包
1.定义
- 将闭包作为
实际参数
传递给函数
- 该闭包是在
函数返回后
再调用
-
异步延迟
调用 - 先
存储
,需要时再调用
-
2. 声明:逃逸闭包前+@escaping
var completionHandler : ((Int)->())?
// 先存储再调用
func makeIncrementer(amount : Int, handler : @escaping (Int) -> ()) {
var runningTotal = 10
runningTotal += amount
// 必须显式引用self,目的是提醒可能有循环引用,自己注意解决
self.completi
onHandler = handler
}
// 异步延迟调用
func async_makeIncrementer(amount : Int, handler : @escaping (Int) -> ()) {
var runningTotal = 10
runningTotal += amount
DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) {
handler(runningTotal)
}
}
重点:逃逸闭包的生命周期
一定要比函数的生命周期长
3.区别非逃逸闭包
非逃逸闭包和逃逸闭包的前提都是:作为函数的参数
非逃逸闭包在
sil
中都是@noescape
修饰在
函数体内
执行,生命周期
跟函数
生命周期一致
非逃逸闭包
不
会产生循环引用
(作用域在函数作用域内)编译器对非逃逸闭包优化:
省略掉内存管理
retain、release逃逸闭包
很耗资源
,不要滥用逃逸闭包
必须显式
引用self
,提醒可能存在循环引用
需要解决。-
自动闭包
- 使用
@autoclosure
关键字声明自动闭包
-
不
接收任何参数
,返回值
是当前内部表达式
的值
- 使用
func debugErrorMsg(_ condition : Bool, _ message : @autoclosure () -> String) {
if condition {
print(message())
}
}
debugErrorMsg(true, "NetWork Error Occured")
上面例子中,实际就是将传入的字符串"NetWork Error Occured"放入到一个闭包表达式中,在调用的时候返回。即:
{
return "NetWork Error Occured"
}