Swift 閉包

前言

本篇文章主要講解Swift中又一個(gè)相當(dāng)重要的知識(shí)點(diǎn) ?? 閉包,首先會(huì)介紹閉包的概念,包含與OC中Block區(qū)別點(diǎn),接著會(huì)從底層分析閉包的原理,最后會(huì)講解一些特殊的閉包的使用場(chǎng)景。

一、概念及使用

什么是閉包? ?? 一個(gè)捕獲了全局上下文的常量或者變量的函數(shù)。閉包在實(shí)現(xiàn)上是一個(gè)結(jié)構(gòu)體,它存儲(chǔ)了一個(gè)函數(shù)(通常是其入口地址)和一個(gè)關(guān)聯(lián)的環(huán)境(相當(dāng)于一個(gè)符號(hào)查找表)

1.1 全局函數(shù)

全局函數(shù)一種特殊的閉包,例如??

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

上面的test是一個(gè)無(wú)捕獲變量的全局函數(shù),也屬于閉包

1.2 內(nèi)嵌函數(shù)

與全局函數(shù)一樣,也是閉包,區(qū)別在于會(huì)捕獲外部變量。例如??

func makeIncrementer() -> () -> Int{
    var runningTotal = 10
    //內(nèi)嵌函數(shù),也是一個(gè)閉包
    func incrementer() -> Int{
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
print(makeIncrementer())  
print(makeIncrementer()()) 

上例中的incrementer內(nèi)嵌函數(shù),也是一個(gè)閉包捕獲了上層函數(shù)的變量runningTotal。運(yùn)行結(jié)果??

1.3 閉包表達(dá)式

閉包表達(dá)式格式如下??

{ (參數(shù))-> 返回類型 in
    // do something
}

具有以下幾個(gè)特點(diǎn):

  1. 是一個(gè)匿名函數(shù)
  2. 所有代碼都在花括號(hào){}內(nèi)
  3. 參數(shù)返回類型in關(guān)鍵字之前
  4. in關(guān)鍵字之后主體內(nèi)容(類似方法體)

看上去是否和OC中的Block相似?OC中的Block的特點(diǎn)是??

  1. 也是個(gè)匿名函數(shù)
  2. 主體內(nèi)容作用域都在花括號(hào){}內(nèi)部
  3. 也具有參數(shù)返回值
  • Swift的閉包可以當(dāng)作let常量var變量,也可以當(dāng)作參數(shù)傳遞??
// 常量
let closure1: (Int) -> Int

// 變量
var closure2 : (Int) -> Int = { (age: Int) in
     return age
}

// 參數(shù)傳遞
func test(params: ()->Int) {
    print(params())
}
  • Swift閉包支持可選類型 ?? 在()外使用?聲明 ??
//聲明一個(gè)可選類型的閉包
// 錯(cuò)誤寫法
var clourse: (Int) -> Int?
clourse = nil

// 正確寫法
var clourse1: ((Int) -> Int)?
clourse1 = nil

1.4尾隨閉包

當(dāng)閉包表達(dá)式作為函數(shù)的最后一個(gè)參數(shù)時(shí),可通過(guò)尾隨閉包的書寫方式來(lái)提高代碼的可讀性??

// test函數(shù):其中最后一個(gè)入?yún)y是一個(gè)閉包
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item: Int, _ item3: Int) -> Bool) -> Bool{
    return by(a, b, c)
}

// 常規(guī)寫法
test(10, 20, 30, by: { (_ itme1: Int, _ itme2: Int, _ itme3: Int) -> Bool in
    return itme1 + itme2 < itme3
})

// 快捷寫法(小括號(hào)提到最后一個(gè)參數(shù)前)
test(10, 20, 30) { (_ itme1: Int, _ itme2: Int, _ itme3: Int) -> Bool in
    return itme1 + itme2 < itme3
}

// 最簡(jiǎn)潔寫法 (入?yún)⒅苯邮褂?0 $1 $2代替,單行代碼可省略return)
test(10, 20, 30) { $0 + $1 < $2 }

上面示例代碼,一步步地簡(jiǎn)寫,到最后,最簡(jiǎn)潔的寫法看上去非常舒服,語(yǔ)義表達(dá)也很清晰。我們平常使用的array.sorted其實(shí)就是一個(gè)尾隨閉包,且這個(gè)函數(shù)就只有一個(gè)參數(shù)??

// array.sorted就是一個(gè)【尾隨閉包】
var array = [1, 2, 3]
// 完整寫法
array.sorted{(item1: Int, item2: Int)->Bool in return item1 < item2}
// 省略參數(shù)類型 ?? 可通過(guò)array中元素的類型推斷
array.sorted{(item1, item2)->Bool in return item1 < item2}
// 省略參數(shù)類型 + 返回值類型 ?? 可通過(guò)return表達(dá)式推斷出返回值類型
array.sorted{(item1, item2) in return item1 < item2}
// 省略參數(shù)類型 + 返回值類型 + return關(guān)鍵字 ?? 單表達(dá)式可以隱士表達(dá),即省略return關(guān)鍵字
array.sorted{(item1, item2) in item1 < item2}
// 簡(jiǎn)寫參數(shù)名稱
array.sorted{return $0 < $1}
// 簡(jiǎn)寫參數(shù)名稱 + 省略return關(guān)鍵字
array.sorted{$0 < $1}
// 最簡(jiǎn)單化,注意 ?? 使用的是【小括號(hào)()】
array.sorted(by: <)

小結(jié)

最后,我們來(lái)總結(jié)一下閉包表達(dá)式的優(yōu)點(diǎn)??

  • 利用上下文推斷參數(shù)返回類型
  • 單表達(dá)式可以隱式返回,省略return關(guān)鍵字
  • 參數(shù)名稱可以直接使用簡(jiǎn)寫(如$0,$1,元組的$0.0)
  • 尾隨閉包可以更簡(jiǎn)潔的表達(dá)

二、閉包底層原理

我們主要探討2個(gè)點(diǎn):

  1. 如何捕獲變量
  2. 閉包的類型值類型還是引用類型

2.1 捕獲變量

首先看看下面的示例輸出什么???

func makeIncrementer() -> () -> Int{
    var runningTotal = 10
    //內(nèi)嵌函數(shù),也是一個(gè)閉包
    func incrementer() -> Int{
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
let makeInc = makeIncrementer()
print(makeInc())
print(makeInc())
print(makeInc())

結(jié)果是不是和你們想的不一樣?因?yàn)?code>runningTotal是局部變量,每次調(diào)用makeIncrementer方法累加1,結(jié)果應(yīng)該都是11,但是事實(shí)卻是11,12,13,為什么會(huì)這樣???

主要原因:內(nèi)嵌函數(shù)捕獲了runningTotal,不再是單純的一個(gè)變量了。

如果是這么調(diào)用??

print(makeIncrementer()())
print(makeIncrementer()())
print(makeIncrementer()())

這次又都是11,和我們之前想的一樣,很有意思。還是老辦法,先從SIL中間層代碼分析??

swiftc -emit-sil xx.swift| xcrun swift-demangle >> ./xx.sil && vscode xx.sil

直接看函數(shù)makeIncrementer()代碼??

上圖可見,內(nèi)嵌函數(shù)incrementer()中的變量runningTotal是通過(guò)alloc_box在堆上開辟的內(nèi)存空間,然后通過(guò)project_box讀取runningTotal,交給閉包使用,所以??

捕獲值的本質(zhì)是 ?? 將變量存儲(chǔ)到堆上

不信,我們還可以打斷點(diǎn)??

看看匯編??

這里也可以看出,在makeIncrementer()方法內(nèi)部調(diào)用了初始化swift_allocObject,使用時(shí)swift_retain,調(diào)用結(jié)束后swift_release,所以我們上面的判斷是正確的。

小結(jié)

  1. 一個(gè)閉包能夠從上下文捕獲已經(jīng)定義的常量變量,并且能夠在其函數(shù)體內(nèi)引用和修改這些值,即使這些定義的常量和變量的原作用域不存在
  2. 修改捕獲值實(shí)際是修改堆區(qū)中的value值
  3. 當(dāng)每次重新執(zhí)行當(dāng)前函數(shù)時(shí),都會(huì)重新創(chuàng)建內(nèi)存空間

所以上面示例中結(jié)果就是??

  1. makeInc是用于存儲(chǔ)makeIncrementer函數(shù)調(diào)用的全局變量,所以每次都需要依賴上一次的結(jié)果
  2. 而直接調(diào)用函數(shù)makeIncrementer()()時(shí),相當(dāng)于每次都新建一個(gè)堆內(nèi)存,所以每次的結(jié)果都存儲(chǔ)在不同的內(nèi)存空間,互不影響,于是每次結(jié)果都是一樣的11。

2.2 閉包的類型

閉包究竟是值類型呢?還是引用類型?先把答案公布出來(lái) ?? 引用類型。也就是上面的實(shí)例代碼中的變量makeInc,它里面存儲(chǔ)的是什么?接下來(lái)我們來(lái)證明一下。

IR基礎(chǔ)語(yǔ)法

老辦法,我們?nèi)ゲ榭?code>SIL中間層代碼,但是發(fā)現(xiàn),通過(guò)SIL并沒有辦法分析出什么,此時(shí)怎么辦呢? ?? 通過(guò)IR代碼來(lái)觀察數(shù)據(jù)的構(gòu)成。所以,我們先來(lái)看看IR基礎(chǔ)語(yǔ)法 ?? 官方文檔

  • 首先,生成IR的命令行??

swiftc -emit-ir xx.swift > ./xx.ll && vscode xx.ll

  • 數(shù)組??
/*
- elementnumber 數(shù)組中存放數(shù)據(jù)的數(shù)量
- elementtype 數(shù)組中存放數(shù)據(jù)的類型
*/
[<elementnumber> x <elementtype>]

// 舉例
/*
24個(gè)i8都是0
- iN:表示多少位的整型,即8位的整型 - 1字節(jié)
*/
alloca [24 x i8], align 8
  • 結(jié)構(gòu)體??
/*
- T:結(jié)構(gòu)體名稱
- <type list> :列表,即結(jié)構(gòu)體的成員列表
*/
//和C語(yǔ)言的結(jié)構(gòu)體類似
%T = type {<type list>}


// 舉例
/*
- swift.refcounted:結(jié)構(gòu)體名稱
- %swift.type*:swift.type指針類型
- i64:64位整型 - 8字節(jié)
*/
%swift.refcounted = type { %swift.type*, i64}
  • 指針類型
<type> *

// 舉例
// 64位的整型 - 8字節(jié)
i64*
  • getelementptr指令
    在LLVM中獲取數(shù)組和結(jié)構(gòu)體的成員時(shí)通過(guò)getelementptr,語(yǔ)法規(guī)則??
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
示例1
// 舉例
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 test() {
    
    munge(array);
    
    return 0;
}

通過(guò)下面的命令將c/c++編譯成IR??

clang -S -emit-llvm 文件名xx > ./.ll && vscode main.ll

// 舉例
clang -S -emit-llvm ${SRCROOT}/SwiftTest/main.c > ./main.ll && vscode main.ll
  • 上圖中倒數(shù)第三行 ?? 第一個(gè)索引 %struct.munger_struct* %13, i32 0 -> 第一個(gè)索引類型 + 第一個(gè)索引值 -> 第一個(gè)索引的偏移量
  • 第二個(gè)索引:i32 0
示例2
int test1() {
    int array[4] = {1, 2, 3, 4};
    int a = array[0];
    return 0;
}

所對(duì)應(yīng)的IR代碼??

示例3

現(xiàn)在基本上熟悉了IR語(yǔ)法規(guī)則了,接下來(lái)我們看之前的例子??

func makeIncrementer() -> (()-> Int){
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
// 函數(shù)變量 (存儲(chǔ)格式是怎樣?)
var makeInc = makeIncrementer()

其IR代碼??

  • 結(jié)構(gòu)體聲明
  1. 先看第3行:聲明一個(gè)名稱為type的結(jié)構(gòu)體,只有一個(gè)成員且是int64類型
  2. 第2行:refcounted結(jié)構(gòu)體 ?? 2個(gè)成員 type + int64
  3. 第1行:function結(jié)構(gòu)體 ?? 2個(gè)成員 指針i8* + refcounted
  4. 第4行:full_boxmetadata ?? 5個(gè)成員 refcounted結(jié)構(gòu)體指針 + 指針i8**(二級(jí)指針) + type + int32 + 指針i8*
  5. 最后一行:TSi結(jié)構(gòu)體模板類,只有一個(gè)成員int64類型
  • main函數(shù)
  • makeIncrementer函數(shù)

上圖中我們發(fā)現(xiàn),外層函數(shù)makeIncrementer是一個(gè)結(jié)構(gòu)體對(duì)象,而內(nèi)嵌函數(shù)incrementer被轉(zhuǎn)換成void *指針,作為外層結(jié)構(gòu)體對(duì)象的一個(gè)成員變量,所以,閉包是引用類型

2.3 偽代碼實(shí)現(xiàn)

上面示例3內(nèi)嵌函數(shù)的示例,我們通過(guò)對(duì)IR代碼的分析,得知了閉包是引用類型,也清楚了整體的一個(gè)實(shí)現(xiàn)流程,現(xiàn)在我們用偽代碼實(shí)現(xiàn)一下該流程(和示例3一樣分為3部分)??

部分1:結(jié)構(gòu)體的定義
  • type結(jié)構(gòu)體
struct Type {
    var type: Int64
}
  • refcounted結(jié)構(gòu)體
struct Refcounted {
    var pointer: UnsafeRawPointer
    var refCount: Int64
}
  • function結(jié)構(gòu)體
struct FunctionData<T> {
    var pointer: UnsafeRawPointer
    var captureValue: UnsafePointer<T>
}
  • full_boxmetadata
struct BoxMetadata<T> {
    var refcounted: UnsafePointer<Refcounted>
    var undefA: UnsafeRawPointer
    var type: Type
    var undefB: Int32
    var undefC: UnsafeRawPointer
}
  • makeIncrementer函數(shù)值有將 refcounted結(jié)構(gòu)體 做了強(qiáng)制轉(zhuǎn)換,如下圖中綠框中的結(jié)構(gòu)體??
struct Box<T> {
    var refcounted: Refcounted
    var value: T  // 8字節(jié)類型,可由外部動(dòng)態(tài)傳入
}
  • 上述IR代碼分析得知,makeIncrementer函數(shù)最后轉(zhuǎn)換成了一個(gè)結(jié)構(gòu)體,原因 ?? 因?yàn)闊o(wú)法直接讀取makeIncrementer返回的是()-> Int,而()-> Int的地址,可以利用結(jié)構(gòu)體地址就是首元素地址的特性,將()-> Int設(shè)為結(jié)構(gòu)體第一個(gè)屬性??
struct VoidIntFunc {
    var f: ()->Int
}

所有結(jié)構(gòu)體已定義完畢,接下來(lái)在用偽代碼實(shí)現(xiàn)一下makeIncrementer函數(shù)??

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

偽代碼實(shí)現(xiàn)??

// 使用struct包裝的函數(shù)
var makeInc = VoidIntFunc(f: makeIncrementer())
// 取地址
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
// 初始化
ptr.initialize(to: makeInc)
// 將指針綁定為FunctionData<Box<Int>>類型,返回指針
let context = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) { $0.pointee }

打印看看

print(context.pointer)
print(context.captureValue.pointee.value)
Mach-O驗(yàn)證
  1. 打開可執(zhí)行二進(jìn)制文件??


  2. 打開終端,輸入??

nm -p 編譯后的machO文件地址 | grep 函數(shù)地址

// 例如:
nm -p /Users/asetku/Library/Developer/Xcode/DerivedData/Demo-bhpsxmnrzusvmeaotyclgmelcxpp/Build/Products/Debug/Demo | grep 00000001000054b0

所以當(dāng)我們var makeInc2 = makeIncrementer()使用時(shí),相當(dāng)于makeInc2也關(guān)聯(lián)了FunctionData結(jié)構(gòu)體,那么makeIncrementer()內(nèi)嵌函數(shù)地址,以及捕獲變量的地址也是同一個(gè),所以才能在上一個(gè)的基礎(chǔ)上進(jìn)行累加

2.4 拓展:函數(shù)內(nèi)部多個(gè)屬性

如果函數(shù)內(nèi)部多個(gè)屬性,在底層對(duì)應(yīng)的結(jié)構(gòu)體是怎樣的呢?示例代碼??

func makeIncrementer() -> (()-> Int){
   var aa = 10
   var bb = 20
   // 內(nèi)嵌函數(shù)(也是一個(gè)閉包,捕獲了runningTotal)
   func incrementer() -> Int {
       aa += 6
       bb += 9
       return bb
   }
   return incrementer
}
var makeInc = makeIncrementer()

查看IR代碼??

  • 基礎(chǔ)結(jié)構(gòu)體聲明?? 沒有變化
  • makeIncrementer函數(shù) ?? 與一個(gè)屬性的相比,多了一個(gè)臨時(shí)結(jié)構(gòu),把兩個(gè)變量分別用指針記錄??
偽代碼
  1. 添加一個(gè)Box2<T>結(jié)構(gòu)體??
struct Box<T> {
   var refcounted: Refcounted
   var value: T  // 8字節(jié)類型,可由外部動(dòng)態(tài)傳入
}

// 多了一個(gè)Box2結(jié)構(gòu),每個(gè)變量都是`Box`結(jié)構(gòu)的對(duì)象
struct Box2<T> {
   var refcounted: RefCounted
   var value1: UnsafePointer<Box<T>>
   var value2: UnsafePointer<Box<T>>
}
  1. 調(diào)用的代碼是這樣??
func makeIncrementer() -> (()-> Int){
  var aa = 10
  var bb = 20

  func incrementer() -> Int {
      aa += 6
      bb += 9
      return bb
  }
  return incrementer
}

var makeInc = VoidFunc(f: makeIncrementer())

let ptr = UnsafeMutablePointer<VoidFunc>.allocate(capacity: 1)

ptr.initialize(to: makeInc)

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

print(context.pointer)
print(context.captureValue.pointee.value1.pointee.value)
print(context.captureValue.pointee.value2.pointee.value)

運(yùn)行??

接著在Mach-O中校驗(yàn)地址??

完美!

2.5 小結(jié)

  1. 捕獲值原理:在堆上開辟內(nèi)存空間,并將捕獲的值放到這個(gè)內(nèi)存空間里
  2. 修改捕獲值時(shí):實(shí)質(zhì)是修改堆空間的值
  3. 閉包是一個(gè)引用類型(引用類型是地址傳遞),閉包的底層結(jié)構(gòu) ?? 結(jié)構(gòu)體:(函數(shù)地址 + 捕獲變量的地址 == 閉包
  4. 函數(shù)也是一個(gè)引用類型(本質(zhì)是一個(gè)結(jié)構(gòu)體,其中只保存了函數(shù)的地址

三、特殊的閉包

最后,完美來(lái)看看一些常用的,特殊的閉包類型:逃逸閉包非逃逸閉包自動(dòng)閉包

3.1 (非)逃逸閉包的概念

  • 逃逸閉包:
    當(dāng)閉包作為函數(shù)的參數(shù),且在函數(shù)返回之后調(diào)用,我們就說(shuō)這個(gè)閉包逃逸了。
    (逃逸閉包作為函數(shù)形參時(shí),需要使用@escaping聲明,生命周期比函數(shù)要長(zhǎng)。如:被外部變量持有異步延時(shí)調(diào)用)
  • 非逃逸閉包:
    系統(tǒng)默認(rèn)閉包參數(shù)是@nonescaping聲明, 是非逃逸閉包,生命周期與被調(diào)用的函數(shù)保持一致

3.1.1 逃逸閉包

大致有2種場(chǎng)景??

  1. 閉包被函數(shù)外變量持有,需要@escaping聲明為逃逸閉包
// 閉包作為屬性
class LGTeacher {
    // 定義一個(gè)閉包屬性
    var complitionHandler: ((Int)->Void)?
    // 函數(shù)參數(shù)使用@escaping修飾,表示允許函數(shù)返回之后調(diào)用
    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("LGTeacher deinit")
    }
}
// 調(diào)用代碼??
var t = LGTeacher()
t.doSomething()
t.complitionHandler?(10)
  1. 閉包被異步線程延時(shí)調(diào)用,需要@escaping聲明為逃逸閉包
class LGTeacher {
    // 定義一個(gè)閉包屬性
    var complitionHandler: ((Int)->Void)?
    // 函數(shù)參數(shù)使用@escaping修飾,表示允許函數(shù)返回之后調(diào)用
    func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){
        var runningTotal = 0
        runningTotal += amount
        // 賦值給屬性
        self.complitionHandler = handler
        
        // 延遲調(diào)用
        DispatchQueue.global().asyncAfter(deadline: .now()+2) {
            print("逃逸閉包延遲執(zhí)行")
            handler(runningTotal)
        }
        print("函數(shù)執(zhí)行完了")
    }

    func doSomething(){
        self.makeIncrementer(amount: 10,handler: {
            print($0)
        })
    }

    deinit {
        print("LGTeacher deinit")
    }
}
// 調(diào)用代碼??
var t = LGTeacher()
t.doSomething()

3.1.2 非逃逸閉包

系統(tǒng)默認(rèn)閉包為非逃逸閉包 ,編譯期自動(dòng)加上@noescape聲明,生命周期與函數(shù)一致。

func test(closure: (()->())) {}

SIL編譯可以看到默認(rèn)使用@noescape聲明閉包??

逃逸閉包 vs 非逃逸閉包 區(qū)別

  • 非逃逸閉包 ?? 一個(gè)接受閉包作為參數(shù)的函數(shù),閉包是在這個(gè)函數(shù)結(jié)束前內(nèi)被調(diào)用,即可以理解為閉包是在函數(shù)作用域結(jié)束前被調(diào)用

    1. 不會(huì)產(chǎn)生循環(huán)引用,因?yàn)?code>閉包的作用域在函數(shù)作用域內(nèi),在函數(shù)執(zhí)行完成后,就會(huì)釋放閉包捕獲的所有對(duì)象
    2. 針對(duì)非逃逸閉包,編譯器會(huì)做優(yōu)化 ?? 省略內(nèi)存管理調(diào)用
    3. 非逃逸閉包捕獲的上下文保存在棧上,而不是堆上(官方文檔說(shuō)明)。
  • 逃逸閉包:一個(gè)接受閉包作為參數(shù)的函數(shù),逃逸閉包可能會(huì)在函數(shù)返回之后才被調(diào)用,即閉包逃離了函數(shù)的作用域

    1. 可能會(huì)產(chǎn)生循環(huán)引用,因?yàn)樘右蓍]包中需要顯式的引用self(猜測(cè)其原因是為了提醒開發(fā)者,這里可能會(huì)出現(xiàn)循環(huán)引用了),而self可能是持有閉包變量的(與OC中block的的循環(huán)引用類似
    2. 一般用于異步函數(shù)的返回,例如網(wǎng)絡(luò)請(qǐng)求
  • 使用建議:如果沒有特別需要,開發(fā)中使用非逃逸閉包是有利于內(nèi)存優(yōu)化的,所以蘋果把閉包區(qū)分為兩種,特殊情況時(shí)再使用逃逸閉包

3.2 自動(dòng)閉包

自動(dòng)識(shí)別閉包返回值,可直接接收返回值類型數(shù)據(jù)
需要用@autoclosure聲明,不接收任何參數(shù)返回值是當(dāng)前內(nèi)部表達(dá)式的值(如()->String)

// 自動(dòng)閉包`@autoclosure`聲明
func testPrint(_ message: @autoclosure ()->String) {
    print(message())
}

func doSomeThing() -> String {
    return "吃了嗎?"
}
// 入?yún)鱜函數(shù)`
testPrint(doSomeThing()) 
// 入?yún)鱜字符串`
testPrint("干啥呢")

可以看到,使用自動(dòng)閉包時(shí),參數(shù)可以是函數(shù),也可以是閉包返回類型(字符串)。
自動(dòng)閉包可以兼容函數(shù)入?yún)㈩愋?/code>(函數(shù)/函數(shù)返參類型)

耗時(shí)場(chǎng)景

再看下面這個(gè)例子??

func testPrint(_ condition: Bool, _ message: String) {
    if condition {
        print("錯(cuò)誤信息: \(message)")
    }
    print("結(jié)束")
}

func doSomeThing() -> String {
    print("執(zhí)行了")
    // 耗時(shí)操作,從0到1000拼接成字符串
    return (0...1000).reduce("") { $0 + " \($1)"}
}

testPrint(false, doSomeThing())

doSomeThing函數(shù)里嗎執(zhí)行了一個(gè)for循環(huán),是一個(gè)耗時(shí)操作,然后將計(jì)算結(jié)果傳給testPrint時(shí),但是testPrintconditionfalse,條件不滿足時(shí),根本不需要關(guān)心message,即不需要執(zhí)行這個(gè)耗時(shí)的操作,所以這里相當(dāng)于做了多余的事。那能否避開這樣的場(chǎng)景呢?

  • 修改入?yún)?br> 將message參數(shù)修改成一個(gè)閉包,需要傳入的是一個(gè)函數(shù)??
func testPrint(_ condition: Bool, _ message: () -> String) {
    if condition {
        print("錯(cuò)誤信息: \(message())")
    }
    print("結(jié)束")
}

func doSomeThing() -> String {
    print("執(zhí)行了")
    // 耗時(shí)操作,從0到1000拼接成字符串
    return (0...1000).reduce("") { $0 + " \($1)"}
}

testPrint(false, doSomeThing)

此時(shí)避免了不必要的耗時(shí)操作,但是,message入?yún)?code>只接收閉包類型,那能否做到message也能接收字符串類型呢??? 是可以的,可以通過(guò)@autoclosure將當(dāng)前的閉包聲明成一個(gè)自動(dòng)閉包,那么message此時(shí)不接收任何參數(shù),返回值是當(dāng)前內(nèi)部表達(dá)式的值??

此時(shí)的message入?yún)⒌拈]包就相當(dāng)于??

{
    //表達(dá)式里的值
    return "something error"
}

總結(jié)

本篇文章圍繞閉包這個(gè)知識(shí)點(diǎn),首先介紹了閉包的概念和使用場(chǎng)景,接著從底層探究了閉包捕獲變量的原理閉包的類型,最后重點(diǎn)介紹了閉包三種常用的特殊場(chǎng)景:逃逸閉包、非逃逸閉包和自動(dòng)閉包,對(duì)于底層原理的分析,希望大家能掌握這種探究問題的思路。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

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

  • 前情提要 Swift的閉包和OC的Block是一回事,是一種特殊的函數(shù)-帶有自動(dòng)變量的匿名函數(shù)。 分別從語(yǔ)法和原理...
    Jacob6666閱讀 428評(píng)論 0 0
  • 閉包 閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。Swift中的閉包與C和Objective-C中的代碼塊...
    浪的出名閱讀 737評(píng)論 0 1
  • 閉包 閉包是?個(gè)捕獲了上下?的常量或者是變量的函數(shù)。 上?的函數(shù)是?個(gè)全局函數(shù),也是?種特殊的閉包,只不過(guò)當(dāng)前的全...
    Mjs閱讀 244評(píng)論 0 0
  • Swift 中的閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。類似于OC中的Block以及其他函數(shù)的匿名函數(shù)...
    喬克_叔叔閱讀 527評(píng)論 1 3
  • 閉包-Closures 自包含的函數(shù)代碼塊 與 C 和 Objective-C 中的代碼塊(blocks))以及其...
    Sunday_David閱讀 187評(píng)論 0 0