Swift底層探索:閉包

閉包是可以在你的代碼中被傳遞和引用的功能性獨(dú)立代碼塊鹉梨。閉包在實(shí)現(xiàn)上是一個結(jié)構(gòu)體常柄,它存儲了一個函數(shù)(通常是其入口地址)和一個關(guān)聯(lián)的環(huán)境(相當(dāng)于一個符號查找表)巷疼。Swift 中的閉包和 C 以及 Objective-C 中的 blocks 很像,還有其他語言中的匿名函數(shù)也類似珊膜。
閉包能夠捕獲和存儲定義在其上下文中的任何常量和變量的引用容握,這也就是所謂的閉合并包裹那些常量和變量,因此被稱為“閉包”车柠,Swift 能夠?yàn)槟闾幚硭嘘P(guān)于捕獲的內(nèi)存管理的操作剔氏。

一、閉包定義

閉包符合如下三種形式中的一種:

  • 全局函數(shù)是一個有名字但不會捕獲任何值的閉包竹祷;
  • 內(nèi)嵌函數(shù)是一個有名字且能從其上層函數(shù)捕獲值的閉包谈跛;
  • 閉包表達(dá)式是一個輕量級語法所寫的可以捕獲其上下文中常量或變量值的沒有名字的閉包。

簡單點(diǎn)說:閉包是一個捕獲了上下文的常量或者變量的函數(shù)塑陵。

全局函數(shù)例子

func test() {
    print("test func")
}

test函數(shù)是一個全局函數(shù)感憾,是特殊閉包,這里沒有捕獲值令花。

內(nèi)嵌函數(shù)例子

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

makeIncrementer是一個官方的例子阻桅,incrementer稱為內(nèi)嵌函數(shù),同時從上層函數(shù)makeIncrementer中捕獲變量runningTotal兼都。

閉包表達(dá)式例子

比如:

{ (age: Int) in
    return age
}

這個就是我們熟知的閉包表達(dá)式嫂沉,是一個匿名函數(shù),而且從上下文中捕獲變量和常量扮碧。

Swift 的閉包表達(dá)式擁有簡潔的風(fēng)格趟章,鼓勵在常見場景中實(shí)現(xiàn)簡潔,無累贅的語法芬萍。常見的優(yōu)化包括:

  • 利用上下文推斷形式參數(shù)和返回值的類型尤揣;
  • 單表達(dá)式的閉包可以隱式返回(省略return關(guān)鍵字);
  • 簡寫實(shí)際參數(shù)名(比如 $0)柬祠;
  • 尾隨閉包語法北戏。

二、閉包表達(dá)式

閉包表達(dá)式一般定義:

{ (parameters) -> (return type) in
    statements
  }

OC中的Block其實(shí)是一個匿名函數(shù)漫蛔,同理閉包表達(dá)式要具備:

  • 作用域(也就是{});
  • 參數(shù)和返回值;
  • 函數(shù)體(in之后的代碼);

閉包作為變量:

var closure : (Int) -> Int = { (age: Int) in
    return age
}

閉包聲明為可選類型(需要表達(dá)式整體可選):

image.png

閉包聲明為常量(賦值后不能改變):
image.png

閉包作為函數(shù)參數(shù):

func test(param: () -> Int) {
    print(param())
}

var age = 18

test {() -> Int in
    age += 10
    return age
}

三嗜愈、尾隨閉包

當(dāng)我們把閉包表達(dá)式作為函數(shù)的最后一個參數(shù)旧蛾,如果閉包表達(dá)式很長,可以通過尾隨閉包的書寫方式提高代碼的可讀性蠕嫁。尾隨閉包是一個被書寫在函數(shù)形式參數(shù)的括號外面(后面)的閉包表達(dá)式锨天,但它仍然是這個函數(shù)的實(shí)際參數(shù)。當(dāng)你使用尾隨閉包表達(dá)式時剃毒,不需要把第一個尾隨閉包寫對應(yīng)的實(shí)際參數(shù)標(biāo)簽病袄。函數(shù)調(diào)用可包含多個尾隨閉包。

func test(_ a: Int, _ b: Int, _ c: Int, by: (_ num1: Int, _ num2 : Int, _ num3: Int) -> Bool) -> Bool {
    return by(a, b, c)
}

上面的test函數(shù)不使用尾隨閉包赘阀,作為()中參數(shù)常規(guī)調(diào)用的情況下如下:

test(10, 20, 30, by: { (num1: Int, num2: Int, num3: Int) -> Bool in
    return (num1 + num2 < num3)
})

在這里我們找方法的調(diào)用比較困難益缠,隨著閉包參數(shù)增加以及嵌套會越來越難找。

我們在調(diào)用的時候編譯器會默認(rèn)格式化為尾隨閉包:

test(10, 20, 30) { (num1, num2, num3) -> Bool in
    return (num1 + num2 < num3)
}

這里我們一看就知道是一個函數(shù)調(diào)用基公,后面是一個閉包表達(dá)式幅慌。{}放在了函數(shù)體外,增加了可讀性轰豆。

假如我們要讀一個數(shù)組排序sort:

var array = [3,1,2]
//普通調(diào)用方式
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })
//閉包作為最后一個參數(shù)胰伍,有且僅有閉包一個參數(shù),可以優(yōu)化為尾隨閉包并且省略小括號
array.sort{(item1 : Int, item2: Int) -> Bool in return item1 < item2 }
//省略參數(shù)類型酸休,根據(jù)array推斷類型
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
//return 是 比較表達(dá)式骂租,返回值Bool省略
array.sort(by: {(item1, item2) in return item1 < item2 })
//單表達(dá)式省略return關(guān)鍵字
array.sort{(item1, item2) in item1 < item2 }
//簡寫實(shí)際參數(shù)
array.sort{ return $0 < $1 }
//省略return關(guān)鍵字
array.sort{ $0 < $1 }
//只傳一個 < 默認(rèn)比較 $0 和 $1,這里必須寫by雨席,by不知道傳的是什么了菩咨。
array.sort(by: <)
//什么都不傳,默認(rèn) <
array.sort()

個人習(xí)慣使用:

array.sort{ $0 < $1 }

四陡厘、捕獲值

繼續(xù)官方makeIncrementer例子抽米,這里便于分析省略了參數(shù)。

func makeIncrementer() -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

let makeInc = makeIncrementer()

調(diào)用:

print(makeInc())
print(makeInc())
print(makeInc())
//輸出
1
2
3

換一下調(diào)用方式:

print(makeIncrementer()())
print(makeIncrementer()())
print(makeIncrementer()())
//輸出
1
1
1

第一種方式的輸出結(jié)果也就意味者runningTotal不僅僅是一個局部變量了糙置。分析下SIL:

image.png

  • alloc_box分配了一個變量給到了runningTotal云茸。runningTotal存儲到了堆上。
  • 閉包調(diào)用前進(jìn)行了retain谤饭,調(diào)用后進(jìn)行了release标捺。

斷點(diǎn)驗(yàn)證:


image.png

閉包捕獲值的本質(zhì)是在堆空間上開辟內(nèi)存空間,將變量放到堆中揉抵。函數(shù)以及捕獲的變量/常量構(gòu)成閉包

總結(jié):

  • 一個閉包能夠偶從上下文捕獲已被定義的常量和變量亡容。即使定義這些常量和變量的原作用域已經(jīng)不存在了,閉包仍能夠在其函數(shù)體內(nèi)引用和修改這些值冤今;
  • 每次修改捕獲值的時候闺兢,修改的都是堆區(qū)中的value;
  • 每次重新執(zhí)行當(dāng)前函數(shù)的時候,都會重新創(chuàng)建內(nèi)存空間戏罢。

五屋谭、閉包是引用類型

在上面的例子中脚囊,將函數(shù)賦值給變量,那么這個時候變量中存儲的是什么桐磁? 通過lldb打印不能很好地看出來存儲的是什么悔耘, 從SIL中看不出返回值是什么。這個時候可以通過IR觀察下數(shù)據(jù)的構(gòu)成我擂。

IR語法

數(shù)組

// 元素?cái)?shù)量    數(shù)據(jù)類型 
[<elementnumber> x <elementtype>]
//example衬以,iN:多少位的整形
alloca [24 x i8], align 8   24個i8都是0,24字節(jié)

結(jié)構(gòu)體

//結(jié)構(gòu)體名稱 結(jié)構(gòu)體成員列表
%T = type {<type list>} // 這種和C語言結(jié)構(gòu)體類似
//example
%swift.refcounted = type { %swift.type*, i64 }

指針

<type> *

//example
i64* //64位整形

getelementptr指令
LLVM中獲取數(shù)組和結(jié)構(gòu)體的成員校摩,通過getelementptr泄鹏,語法規(guī)則如下:

<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*

看一個LLVM官網(wǎng)當(dāng)中的例子:

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];

int main(int argc, const char * argv[]) {
    // insert code here...
    munge(array);
    return 0;
}

為了方便閱讀,注釋掉main生成IR文件:

rm -rf ${SRCROOT}/main.ll
clang ${SRCROOT}/IRTest/main.c -emit-llvm -S -c >> ./main.ll && open main.ll

IR代碼:

;結(jié)構(gòu)體
%struct.munger_struct = type { i32, i32 }
;array 數(shù)量3 結(jié)構(gòu)體類型
@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) #0 {
  ;創(chuàng)建內(nèi)存空間秧耗,存放結(jié)構(gòu)體首地址。%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
  ;第一個結(jié)構(gòu)體
  %4 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
  ;第一個結(jié)構(gòu)體第一個元素
  %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
  ;第二個結(jié)構(gòu)體
  %8 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %7, i64 2
  ;第二個結(jié)構(gòu)體第二個元素
  %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
  ;取到結(jié)構(gòu)體基地址舶治,數(shù)組的首地址
  %12 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  ;index = 0分井,基地址偏移0。0 *結(jié)構(gòu)體大小霉猛。拿到結(jié)構(gòu)體的第0個地址尺锚。
  %13 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %12, i64 0
  ;i32 0 相對于自己偏移0。拿到結(jié)構(gòu)體首地址惜浅,第二個i32 0 拿到結(jié)構(gòu)體第一個元素
  %14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0
  store i32 %11, i32* %14, align 4
  ret void
}
image.png
 int array[4] = {1, 2, 3, 4};
 int a = array[0];

這里的int a = array[0]對應(yīng)到LLVM的代碼如下:

a = getelementptr inbounds [4 x i32], [4 x i32]* array, i64 0, i64 0

也就是下面這樣的圖:


image.png

第一個指針使用基本數(shù)據(jù)類型[4 * i32]瘫辩,返回的指針前進(jìn)0*16字節(jié),也就是數(shù)組首地址坛悉。第二個index伐厌,返回基本類型i32前進(jìn)0字節(jié),就是當(dāng)前數(shù)組第一個元素裸影。返回的指針類型為i32*挣轨。

  • 第一個索引(%struct.munger_struct* %13, i32 0)不改變返回的指針類型(%14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0%14最終存儲i32)轩猩。ptrval(ptrval這里對應(yīng)%13)前面的*對應(yīng)什么類型(%struct.munger_struct)就返回什么類型卷扮。
  • 第一個索引的偏移量由第一個索引的值(i32 0)和第一個ty(%struct.munger_struct* %13)指定的基本類型共同確定。也就是%struct.munger_struct* %13, i32 0均践。
  • 后面的索引都是在數(shù)組/結(jié)構(gòu)體內(nèi)進(jìn)行索引晤锹。
  • 每增加一個索引,就會使得該索引使用的基本類型和返回的指針類型去掉一層彤委。(也就是上面的例子鞭铆,第一個索引是[4 * i32]去掉一層是i32)。

GEP作用于結(jié)構(gòu)體時葫慎,索引一定要是常量衔彻。該指令只是返回一個偏移后的指針薇宠,并沒有訪問內(nèi)存。

閉包IR分析

回到makeIncrementer的例子艰额,分析下makeIncrementerIR

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  %3 = call swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"()
  %4 = extractvalue { i8*, %swift.refcounted* } %3, 0
  %5 = extractvalue { i8*, %swift.refcounted* } %3, 1
  store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : () -> Swift.Int", i32 0, i32 0), align 8
  store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : () -> Swift.Int", i32 0, i32 1), align 8
  ret i32 0
}
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是swift_allocObject分配的內(nèi)存空間
  %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
  ;8字節(jié)存值澄港,按位轉(zhuǎn)換創(chuàng)建出來的HeapObject
  %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是%TSi類型的地址空間
  %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  ;把0存儲到 _value,也就是[8 x i8]連續(xù)的地址空間中柄沮。
  store i64 0, i64* %._value, align 8
  %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
  call void @swift_release(%swift.refcounted* %1) #1
  ;往結(jié)構(gòu)體中插入值回梧。
  ;`bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*)` 
  ;bitcast 按位轉(zhuǎn)換 把內(nèi)嵌函數(shù)incrementer為void*存到`i8*`內(nèi)存空間中。
  ;之后插入 %swift.refcounted* %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
  ;返回一個結(jié)構(gòu)體`%6`祖搓,結(jié)構(gòu)體包含一個 `i8* `和 `%swift.refcounted*`
  ret { i8*, %swift.refcounted* } %6
}

IRiN表示整數(shù)類型狱意,i1表示布爾類型~i8*代表void*拯欧。
bitcase在進(jìn)行指向很轉(zhuǎn)換的過程中sourceType的位大小和destType必須相同详囤。如果sourceType為指針,則destType也必須是相同大小的指針镐作,轉(zhuǎn)換就好像是該值已存儲到內(nèi)存中并作為destType類型讀取一樣藏姐。類似于SwiftunsafeBitCast

%swift.refcounted*定義如下:

%swift.function = type { i8*, %swift.refcounted* }
;結(jié)構(gòu)體该贾,結(jié)構(gòu)體包含{結(jié)構(gòu)體羔杨,i64位整形}
%swift.refcounted = type { %swift.type*, i64 }; = {i64,i64} //heapObject
;結(jié)構(gòu)體存放i64位整形
%swift.type = type { i64 }
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%TSi = type <{ i64 }>
  • makeIncrementer返回返回一個結(jié)構(gòu)體%6,結(jié)構(gòu)體包含一個 i8*%swift.refcounted*杨蛋。這里的i8*可以理解為void*兜材,swift.refcounted*是一個結(jié)構(gòu)體指針。
  • %swift.refcounted*是一個heapObject

還原下makeIncrementer返回值如下:

struct FunctionData {
    var ptr: UnsafeRawPointer
    var unKnown: UnsafeRawPointer
}

由于%swift.refcounted*是一個heapObject,還原后如下:

struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refcount2: UInt32
}

FunctionData結(jié)構(gòu)體中的unKnown就是heapObjectindex1的地方插入了_value(就是捕獲的值)逞力,所以unKnown的結(jié)構(gòu)體如下:

struct Box<T> {
    var refCounted:HeapObject
    var value: T
}
//function就變成了
struct FunctionData<BoxType> {
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<BoxType>
}

嘗試綁定觀察下:

image.png

改變下參數(shù)類型試試(() -> Int):

//改變下類型
let makeInc = makeIncrementer()
let ptr = UnsafeMutablePointer<() -> Int>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1){ $0.pointee }
print(funcationData.ptr)
print(funcationData.captureValue)

查看下funcationData.ptr的地址

(lldb) cat address 0x00000001000029c0
&0x00000001000029c0,  partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed () -> (@unowned Swift.Int) to @escaping @callee_guaranteed () -> (@out Swift.Int) <+0> , ($sSiIegd_SiIegr_TRTA)SwiftClouse.__TEXT.__text

恢復(fù)下這個函數(shù)的符號,看到綁定的并不是內(nèi)嵌函數(shù):


image.png

所以最終對makeInc包裝一層:

struct VoidIntFunc {
    var f:() -> Int
}

完整代碼如下:

struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refcount2: UInt32
}

struct Box<T> {
    var refCounted:HeapObject
    var value: T //捕獲值
}

struct FunctionData<BoxType> {
    var ptr: UnsafeRawPointer //內(nèi)嵌函數(shù)地址
    var captureValue: UnsafePointer<BoxType> //捕獲值地址
}

// 這里包裝為了返回值不受影響曙寡。
struct VoidIntFunc {
    var f:() -> Int
}

func makeIncrementer() -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

let makeInc = VoidIntFunc(f: makeIncrementer())
//拿到包裝的結(jié)構(gòu)體地址
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
//ptr指向f
ptr.initialize(to: makeInc)
//把f綁定到FunctionData結(jié)構(gòu)體
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1){
    $0.pointee
}
//funcationData中第一個值ptr,也就是內(nèi)嵌函數(shù)地址
print(funcationData.ptr)
//funcationData中第二個指針指向的值掏击。也就是捕獲值
print(funcationData.captureValue.pointee)

輸出

0x0000000100002bd0
Box<Int>(refCounted: SwiftClouse.HeapObject(type: 0x0000000100004038, refCount1: 3, refcount2: 2), value: 0)

在終端搜索下0x0000000100002bd0對應(yīng)的符號表:

nm -p /Users/**/Library/Developer/Xcode/DerivedData/SwiftClouse-fxzfztmmgwufmzgqbbajkncgvxrb/Build/Products/Debug/SwiftClouse | grep 0000000100002bd0

輸出

0000000100002bd0 t _$s11SwiftClouse15makeIncrementerSiycyF11incrementerL_SiyFTA

可以看到0x0000000100002bd0就是內(nèi)嵌函數(shù)的地址卵皂。

那么如果是捕獲多個值呢?

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

IR代碼:
返回值相同:

  ret { i8*, %swift.refcounted* } %12

%12:

 %12 = insertvalue { i8*, %swift.refcounted* } { 
    ;強(qiáng)轉(zhuǎn)內(nèi)嵌函數(shù)地址放入 i8* 中
    i8*  bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer(forIncrement: Swift.Int) -> () -> Swift.Int" 
  to i8*),  %swift.refcounted* undef }, 
  ;%8 插入 index1 也就是 %swift.refcounted*
  %swift.refcounted* %8, 1

%8:

 %8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* 
  ;類型是 full_boxmetadata砚亭。
  getelementptr inbounds (%swift.full_boxmetadata, 
  ;i32 0 相對于 %swift.full_boxmetadata 的偏移0灯变。
  %swift.full_boxmetadata* @metadata.3, i32 0, 
  ; %swift.full_boxmetadata 地址 index2
  i32 2), 
  ;分配32字節(jié)
  i64 32, 
  ;8字節(jié)對齊
  i64 7) #2

%swift.full_boxmetadata

; index 2 取的是 %swift.type
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%swift.refcounted = type { %swift.type*, i64 }
;i64
%swift.type = type { i64 }

完整解讀:

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  ret i32 0
}
define hidden swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer(forIncrement: Swift.Int) -> () -> Swift.Int"(i64 %0) #0 {
entry:
  %amount.debug = alloca i64, align 8
  %1 = bitcast i64* %amount.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
  %runningTotal.debug = alloca %TSi*, align 8
  %2 = bitcast %TSi** %runningTotal.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
  ;存儲 amount 到 %amount.debug
  store i64 %0, i64* %amount.debug, align 8
  ;開辟空間
  %3 = 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) #2
  %4 = bitcast %swift.refcounted* %3 to <{ %swift.refcounted, [8 x i8] }>*
  %5 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %4, i32 0, i32 1
  %6 = bitcast [8 x i8]* %5 to %TSi*
  store %TSi* %6, %TSi** %runningTotal.debug, align 8
  ;獲日纸伞(%swift.refcounted*  index0)地址給到_value
  %._value = getelementptr inbounds %TSi, %TSi* %6, i32 0, i32 0
  ;一卒蘸、10存入_value(%swift.refcounted*  index0)也就是runningTotal
  store i64 10, i64* %._value, align 8
  %7 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %3) #2
  ;開辟空間鬓照,取swift Type 地址
  %8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* 
  ;類型是 full_boxmetadata匾浪。
  getelementptr inbounds (%swift.full_boxmetadata, 
  ;i32 0 相對于 %swift.full_boxmetadata 的偏移0挟冠。
  %swift.full_boxmetadata* @metadata.3, i32 0, 
  ; %swift.full_boxmetadata 地址 index2
  i32 2), 
  ;分配32字節(jié)
  i64 32, 
  ;8字節(jié)對齊
  i64 7) #2
  %9 = bitcast %swift.refcounted* %8 to <{ %swift.refcounted, %swift.refcounted*, %TSi }>*
  ;index1也就是新開辟的(%swift.refcounted*)
  %10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1
  ;%3存儲到%10噩咪,新開辟的空間也就是%8弄慰。%3就是runningTotal 這里是**地址的地址勺远。所以runningTotal獲取要通過地址的地址。
  ;二耙替、runningTotal存入到新開辟的 %swift.refcounted** index 1
  store %swift.refcounted* %3, %swift.refcounted** %10, align 8
  ;index2(%swift.refcounted)
  %11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2
  ;取地址 index2的 %swift.refcounted 的index0
  %._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0
  ;三亚侠、把a(bǔ)mcount存儲到_value1(新開辟的%8的index2的 %swift.refcounted 的index0),這里也就是runningTotal(**)在amcount(*)前
  store i64 %0, i64* %._value1, align 8
  call void @swift_release(%swift.refcounted* %3) #2
  ;四俗扇、index1 插入 %8 結(jié)構(gòu)體(runningTotal(**)在amcount(*))
  %12 = insertvalue { i8*, %swift.refcounted* } { 
    ;強(qiáng)轉(zhuǎn)內(nèi)嵌函數(shù)地址放入 i8* 中
    i8*  bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer(forIncrement: Swift.Int) -> () -> Swift.Int" 
  to i8*),  %swift.refcounted* undef }, 
  ;%8(runningTotal + amount) 插入 index1 也就是 %swift.refcounted*
  %swift.refcounted* %8, 1
 ;返回值沒有變
  ret { i8*, %swift.refcounted* } %12
}
  • 先存儲runningTotal(**)再存儲amcount(*)
  • 最終一起插入結(jié)構(gòu)體%swift.refcounted*中包裝返回{ i8*, %swift.refcounted* }
    再看下ptrcaptureValue驗(yàn)證下:
let makeInc = VoidIntFunc(f: makeIncrementer(forIncrement: 20))
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1){
    $0.pointee
}
print(funcationData.ptr)
print(funcationData.captureValue)

2個捕獲值的情況

1個捕獲值的情況

再還原下結(jié)構(gòu)體box:

struct Box<T1,T2> {
    var refCounted:HeapObject
    var valueBox: UnsafePointer<T1>
    var value: T2 //捕獲
}

調(diào)用修改:

let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int,Int>>.self, capacity: 1){
    $0.pointee
}

驗(yàn)證:


image.png

多捕獲幾個值看看:

func makeIncrementer(_ amount: Int, _ amount1: Int) -> () -> Int {
    var runningTotal = 1
    var runningTota1 = 2
    func incrementer() -> Int {
        runningTotal += amount
        runningTota1 += amount1
        return runningTotal
    }
    return incrementer
}

let makeInc = VoidIntFunc(f: makeIncrementer(10,11))
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int,Int>>.self, capacity: 1){
    $0.pointee
}
print(funcationData.ptr)
print(funcationData.captureValue)
image.png
  • 按捕獲的順序存儲硝烂,局部變量是指針的指針,參數(shù)是指針铜幽。

總結(jié):

  • 捕獲值原理的本質(zhì)是在堆區(qū)開辟一塊空間滞谢,把當(dāng)前捕獲的值放在開辟的空間上。相當(dāng)于把值用box包裝了一層除抛。
  • 修改捕獲值的時候去堆上把變量拿出來
  • 閉包是引用類型(地址傳遞)狮杨,閉包的底層結(jié)構(gòu)是結(jié)構(gòu)體:FuncationData存儲了函數(shù)地址和捕獲值地址

函數(shù)也是引用類型

func makeIncrementer(amount: Int) -> Int {
    let runningTotal = 10
    return runningTotal + amount
}
var makeInc = makeIncrementer

對應(yīng)的IR:

;i8*和 %swift.refcounted*
%swift.function = type { i8*, %swift.refcounted* }
define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  ;函數(shù)地址轉(zhuǎn)換為void* 放到 %swift.function 結(jié)構(gòu)體第一個元素 index0
  store i8* bitcast (i64 (i64)* @"main.makeIncrementer(amount: Swift.Int) -> Swift.Int" to i8*), i8** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : (Swift.Int) -> Swift.Int", i32 0, i32 0), align 8
  ;index1 存 null
  store %swift.refcounted* null, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : (Swift.Int) -> Swift.Int", i32 0, i32 1), align 8

代碼驗(yàn)證:

struct FunctionData {
    var ptr: UnsafeRawPointer //內(nèi)嵌函數(shù)地址
    var captureValue: UnsafeRawPointer? //捕獲值地址
}

// 這里包裝為了返回值不受影響
struct VoidIntFunc {
    var f:(Int) -> Int
}

func makeIncrementer(amount: Int) -> Int {
    let runningTotal = 10
    return runningTotal + amount
}

let makeInc = VoidIntFunc(f: makeIncrementer)
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData.self, capacity: 1){
    $0.pointee
}
print(funcationData.ptr)
print(funcationData.captureValue)
image.png

函數(shù)的本質(zhì)傳遞的是結(jié)構(gòu)體{函數(shù)地址,null}到忽,結(jié)構(gòu)體只有指針橄教,捕獲值為null。所以函數(shù)是引用類型喘漏。

六颤陶、逃逸閉包

當(dāng)閉包作為一個實(shí)際參數(shù)傳遞給一個函數(shù)的時候,并且是在函數(shù)返回之后調(diào)用陷遮,我們就說這個閉包逃逸了。當(dāng)我們聲明一個接受閉包作為形式參數(shù)的函數(shù)時垦江,在形式參數(shù)前加@escaping來明確閉包是允許逃逸的帽馋。Swift 3.0之后,系統(tǒng)默認(rèn)閉包參數(shù)是@noescaping比吭。

非逃逸閉包

func makeIncrementer(amount: Int, handler: (Int) -> Void) {
    handler(amount)
}

對應(yīng)的SIL

image.png

非逃逸閉包(@noescaping
1.函數(shù)體內(nèi)執(zhí)行;
2.函數(shù)執(zhí)行完之后绽族,閉包表達(dá)式消失。

  • 非逃逸閉包不會產(chǎn)生循環(huán)引用衩藤;
  • 非逃逸閉包編譯器會做優(yōu)化吧慢,省略掉一些內(nèi)存管理調(diào)用;
  • 非逃逸閉包上下文會優(yōu)化保存在棧上而不是堆上赏表;(這個沒有驗(yàn)證出來)

逃逸閉包

存儲調(diào)用

class HotpotCat {
    var complitionHandler: ((Int) -> Void)?
    
    func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void) {
        var runningTotal = 0
        runningTotal += amount
        self.complitionHandler = handler
    }
    
    func doSomething() {
        self.makeIncrementer(amount: 10) {
            print($0)
        }
    }
    deinit {
        print("LGTeaher deinit")
    }
}

var hp = HotpotCat()
hp.doSomething()
hp.complitionHandler?(10)
10

這里hp并沒有釋放检诗。

class HotpotCat {    
    func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void) {
        var runningTotal = 0
        runningTotal += amount
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            handler(runningTotal)
        }
    }
    
    func doSomething() {
        self.makeIncrementer(amount: 10) {
            print($0)
        }
    }
    deinit {
        print("LGTeaher deinit")
    }
}

//didfinishlaunch中調(diào)用(runloop)
 var hp = HotpotCat()
 hp.doSomething()
10
LGTeaher deinit

這里hp釋放了间狂。

逃逸閉包(@escaping
1.閉包作為參數(shù)傳遞給函數(shù)何鸡;
2.函數(shù)返回之后調(diào)用氢惋。(延遲調(diào)用/存儲骚亿,后續(xù)調(diào)用)
由于逃逸閉包很耗資源俱笛,除了上面兩種情況盡量不要聲明為逃逸閉包浆兰。逃逸閉包顯示引用self提示有可能造成循環(huán)引用,自己注意解決轿腺。

七、自動閉包

自動閉包是一種自動創(chuàng)建的用來把作為實(shí)際參數(shù)傳遞給函數(shù)的表達(dá)式打包的閉包喧务。它不接受任何實(shí)際參數(shù)孽亲,并且當(dāng)它被調(diào)用時玲昧,它會返回內(nèi)部打包的表達(dá)式的值亲配。好處在于通過寫普通表達(dá)式代替顯式閉包而使你省略包圍函數(shù)形式參數(shù)的括號思灰。

濫用自動閉包會導(dǎo)致你的代碼難以讀懂。上下文和函數(shù)名應(yīng)該寫清楚求值是延遲的肺魁。如果你想要自動閉包允許逃逸怎诫,就同時使用 @autoclosure 和 @escaping 標(biāo)志舱沧。

func myDebugPrint(_ condition: Bool , _ message: String){
    if condition {
        print("debug:\(message)")
    }
}

myDebugPrint(true, "Application Error Occured")

上面例子比如條件控制打印一些log信息。條件為true的時候打印玄窝。message是一個字符串牵寺,那么如果message是別的功能模塊生成的呢?

func myDebugPrint(_ condition: Bool , _ message: String){
    if condition {
        print("debug:\(message)")
    }
}

func test() -> String {
    return "Application Error Occured"
}

myDebugPrint(true, test())

這個時候如果條件為false恩脂,test函數(shù)依然會調(diào)用帽氓,本質(zhì)上就造成了浪費(fèi)。
message改成閉包在條件為true的時候執(zhí)行閉包

func myDebugPrint(_ condition: Bool , _ message: () -> String){
    if condition {
        print(message())
    }
}

func test() -> String {
    return "Application Error Occured"
}

myDebugPrint(true, test)

這個時候基本完美了东亦,但如果這塊模塊是一個公用模塊杏节,是不是應(yīng)該兼容messageString的情況?
這個時候自動閉包就派上用場了

func myDebugPrint(_ condition: Bool , _ message: @autoclosure () -> String){
    if condition {
        print(message())
    }
}

func test() -> String {
    return "Application Error Occured"
}

myDebugPrint(false, test())
myDebugPrint(true, "Network Error")
  • 這里test()要帶()和普通閉包不同典阵,只會在conditiontrue的情況下調(diào)用test()奋渔。
  • myDebugPrint(true, "Network Error") 參數(shù)相當(dāng)于用閉包包裹字符串直接return:
{
    return "Network Error"
}

官方文檔:閉包

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市壮啊,隨后出現(xiàn)的幾起案子嫉鲸,更是在濱河造成了極大的恐慌,老刑警劉巖歹啼,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玄渗,死亡現(xiàn)場離奇詭異,居然都是意外死亡狸眼,警方通過查閱死者的電腦和手機(jī)藤树,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拓萌,“玉大人岁钓,你說我怎么就攤上這事∥⑼酰” “怎么了屡限?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炕倘。 經(jīng)常有香客問我钧大,道長,這世上最難降的妖魔是什么罩旋? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任啊央,我火速辦了婚禮眶诈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘劣挫。我一直安慰自己册养,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布压固。 她就那樣靜靜地躺著球拦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪帐我。 梳的紋絲不亂的頭發(fā)上坎炼,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音拦键,去河邊找鬼谣光。 笑死,一個胖子當(dāng)著我的面吹牛芬为,可吹牛的內(nèi)容都是我干的萄金。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼媚朦,長吁一口氣:“原來是場噩夢啊……” “哼氧敢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起询张,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤孙乖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后份氧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唯袄,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年蜗帜,在試婚紗的時候發(fā)現(xiàn)自己被綠了恋拷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡厅缺,死狀恐怖蔬顾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情店归,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布酪我,位于F島的核電站消痛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏都哭。R本人自食惡果不足惜秩伞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一逞带、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纱新,春花似錦展氓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至簿废,卻和暖如春空入,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背族檬。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工歪赢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人单料。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓扫尖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藏斩。 傳聞我的和親對象是個殘疾皇子躏结,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 閉包 閉包是?個捕獲了上下?的常量或者是變量的函數(shù)狰域。 上?的函數(shù)是?個全局函數(shù),也是?種特殊的閉包兆览,只不過當(dāng)前的全...
    Mjs閱讀 231評論 0 0
  • 前情提要 Swift的閉包和OC的Block是一回事屈溉,是一種特殊的函數(shù)-帶有自動變量的匿名函數(shù)。 分別從語法和原理...
    Jacob6666閱讀 414評論 0 0
  • swift進(jìn)階 學(xué)習(xí)大綱[http://www.reibang.com/p/0fc67b373540] 在 sw...
    markhetao閱讀 872評論 0 3
  • 一笛谦、閉包 1.1昌阿、閉包表達(dá)式(Closure Expression)在 Swift 里面可以通過函數(shù) func 定...
    IIronMan閱讀 979評論 0 2
  • 在上一篇文章Swift中的變量和常量中我總結(jié)了一些自己對于變量和常量的認(rèn)識宝泵,最近學(xué)習(xí)了閉包,順便給大家分享一下關(guān)于...
    老板娘來盤一血閱讀 18,673評論 16 87