-
閉包的定義
閉包是一個捕獲
了上下文
的常量或變量的匿名函數
。
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"
}