Swift底層進階--011:閉包

什么是閉包

維基百科中的解釋:

  • 在計算機科學(xué)中,閉包(Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是在支持頭等函數(shù)的編程語言中實現(xiàn)詞法綁定的一種技術(shù)。
  • 閉包在實現(xiàn)上是一個結(jié)構(gòu)體,它存儲了一個函數(shù)(通常是其入口地址)和一個關(guān)聯(lián)的環(huán)境(相當(dāng)于一個符號查找表)。
func test() {
    print("test")
}

上述代碼,test是?個全局函數(shù),也是?種特殊的閉包,只不過當(dāng)前全局函數(shù)并不捕獲值

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

上述代碼,incrementer稱之為內(nèi)嵌函數(shù),同時從上層函數(shù)makeIncrementer中捕獲變量runningTotal

{(age: Int) -> Int in
    return age
}

上述代碼,稱之為閉包表達式,是?個匿名函數(shù),可以從上下?中捕獲變量和常量

閉包表達式

閉包表達式是Swift語法,可以更簡潔的傳達信息,具有以下特性:

  • 利?上下?推斷參數(shù)和返回值類型
  • 單表達式可以隱?返回,既省略return關(guān)鍵字
  • 參數(shù)名稱可簡寫,?如$0
  • 尾隨閉包表達式

閉包表達式書寫格式:

{(param) -> ReturnType in
   //函數(shù)體
}
  • 作?域,也就是?括號
  • 參數(shù)和返回值
  • 函數(shù)體,也就是in之后的代碼

Swift中閉包即可以當(dāng)做變量,也可以當(dāng)做參數(shù)傳遞

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

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

test(param: closure)

可以將閉包聲明?個可選類型,需要在參數(shù)和返回值外整體加()?

//錯誤寫法
//var closure: (Int) -> Int?
//closure = nil

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

注釋中的錯誤寫法,相當(dāng)于僅返回值是可選類型

可以通過let關(guān)鍵字將閉包聲明為?個常量,?旦賦值后不可改變

let closure: (Int) -> Int

closure = {(age: Int) in
   return age
}
尾隨閉包

把閉包表達式作為函數(shù)的最后?個參數(shù)。如果當(dāng)前閉包表達式很?,可以通過尾隨閉包的書寫?式來提?代碼的可讀性

func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool{
   return  by(a, b, c)
}

//常規(guī)寫法
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)
}

尾隨閉包寫法,?眼看上去就知道是函數(shù)的調(diào)?。后?是?個閉包表達式,{}放在函數(shù)外?

var array = [1, 2, 3]

//常規(guī)寫法
array.sort{(item1 : Int, item2: Int) -> Bool in return item1 < item2 }

//省略參數(shù)類型,根據(jù)上下文自動推斷
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })

//省略返回值類型,根據(jù)上下文自動推斷
array.sort(by: {(item1, item2) in return item1 < item2 })

//單表達式可省略return關(guān)鍵字
array.sort{(item1, item2) in item1 < item2 }

//參數(shù)簡寫,通過$0、$1、$2...按順序替代對應(yīng)位置的參數(shù)
array.sort{ return $0 < $1 }

//單表達式可省略return關(guān)鍵字
array.sort{ $0 < $1 }

//使用高階函數(shù),指定排序規(guī)則
array.sort(by: <)

上述代碼,展示了閉包表達式的好處:

  • 使用尾隨閉包,{}放在函數(shù)外?,提?可讀性
  • 利?上下?推斷參數(shù)和返回值類型
  • 單表達式可以隱?返回,可省略return關(guān)鍵字
  • 參數(shù)簡寫,通過$0、$1、$2...按順序替代對應(yīng)位置的參數(shù)
  • 最后的代碼并不是尾隨閉包的寫法,使用高階函數(shù)指定排序規(guī)則,可以只傳入<,這種方式by:不能省略
捕獲值
func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

print("makeIncrementer:\(makeIncrementer()())")
print("makeIncrementer:\(makeIncrementer()())")
print("makeIncrementer:\(makeIncrementer()())")
print("--------------------")

let makeInc = makeIncrementer()

print("makeInc:\(makeInc())")
print("makeInc:\(makeInc())")
print("makeInc:\(makeInc())")

//輸出以下內(nèi)容:
//makeIncrementer:11
//makeIncrementer:11
//makeIncrementer:11
//--------------------
//makeInc:11
//makeInc:12
//makeInc:13

上述代碼中,使用makeIncrementer()()調(diào)用,每次都重新分配runningTotal變量并進行+1,所以輸出結(jié)果都是11。但使用makeInc()調(diào)用,內(nèi)嵌函數(shù)incrementer內(nèi)部捕獲了runningTotal變量,所以輸出結(jié)果是11、12、13

通過SIL代碼,分析makeIncrementer函數(shù)是如何捕獲變量的

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

makeIncrementer

alloc_box官方文檔說明:在堆上分配一塊內(nèi)存空間,存儲了metadatarefCount、當(dāng)前的value

alloc_box

通過斷點查看匯編代碼,確實調(diào)?了swift_allocObject?法

匯編代碼

  • 捕獲變量的本質(zhì):就是在堆上開辟內(nèi)存空間,將當(dāng)前的變量存儲到里面
  • 閉包的本質(zhì):就是當(dāng)前的內(nèi)嵌函數(shù),加上捕獲的變量或者常量
  • ?個閉包能夠從上下?捕獲已被定義的常量和變量。即使定義這些常量和變量的原作?域已經(jīng)不存在, 閉包仍能夠在其函數(shù)體內(nèi)引?和修改這些值
  • 每次修改捕獲值的時候,修改的是堆區(qū)中的value
  • 每次重新執(zhí)?當(dāng)前函數(shù)的時候,都會重新創(chuàng)建內(nèi)存空間
閉包是引?類型

makeIncrementer函數(shù)賦值給變量,這時變量??存儲的是什么?是函數(shù)地址嗎?

這?通過斷點查看匯編代碼,無法看出makeInc變量存儲的是什么

匯編代碼

這時可以把SIL代碼再降?級,通過IR來觀察數(shù)據(jù)的構(gòu)成

IR語法
  • 數(shù)組
[<elementnumber> x <elementtype>]
//example
alloca [24 x i8], align 8 //24個i8都是0
  • 結(jié)構(gòu)體
%swift.refcounted = type { %swift.type*, i64 }
//表示形式
%T = type {<type list>} //這種和C語?的結(jié)構(gòu)體類似
  • 指針類型
<type> *
//example
i64* //64位的整形
  • getelementptr指令
    LLVM中獲取數(shù)組和結(jié)構(gòu)體的成員,通過getelementptr指令:
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*

創(chuàng)建test.c文件,運行一個來自LLVM官?的getelementptr指令的案例

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[]) {
//
//    munge(array);
//    return 0;
//}

將上述代碼生成IR文件:clang -S -fobjc-arc -emit-llvm test.c

IR

結(jié)合以下代碼理解?下

int array[4] = {1, 2, 3, 4};
int a = array[0];

其中int a = array[0]這句對應(yīng)LLVM代碼應(yīng)該是這樣的:

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

結(jié)合下面這張圖,可以看到第?個0,使?基本類型[4 * i32],因此返回的指針前進 0 * 16字節(jié),也就是當(dāng)前數(shù)組的?地址。第?個index,使?的基本類型是 i32,返回的指針前進0字節(jié),也就是當(dāng)前數(shù)組的第?個元素。返回的指針類型為i32 *

  • 第?個索引不會改變返回指針的類型,也就是說ptrval前?的*對應(yīng)什么類型,就返回什么類型
  • 第?個索引的偏移量是由第?個索引的值和第?個ty指定的基本類型共同確定的
  • 后?的索引是在數(shù)組或結(jié)構(gòu)體內(nèi)進?索引
  • 每增加?個索引,就會使該索引使?的基本類型和返回指針的類型去掉?層

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

了解IR語法后,回到makeIncrementer案例,通過IR代碼,查看makeInc變量存儲的是什么

將上述代碼生成IR文件:swiftc -emit-ir main.swift | xcrun swift-demangle

swift.refcounted

找到main.makeIncrementer()函數(shù)

makeIncrementer

bitcast在進?指針類型轉(zhuǎn)換的過程中,sourceType的位??和destType必須相同。如果源類型是指針,則?標類型也必須是相同??的指針,轉(zhuǎn)換就好像該值已存儲到內(nèi)存中并作為destType類型讀取?樣。類似Swift中的unsafeBitCast

定義FuntionData結(jié)構(gòu)體,仿照IRswift.function進行代碼還原

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

定義HeapObject結(jié)構(gòu)體,相當(dāng)于swift.refcounted

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

定義Box結(jié)構(gòu)體,相當(dāng)于swift.full_boxmetadata

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

定義完所有結(jié)構(gòu)體,在使用過程中,makeInc無法直接綁定為FuntionData<Box<Int>>類型,因為編譯器無法推斷出具體類型

編譯報錯

定義VoidIntFun結(jié)構(gòu)體,用結(jié)構(gòu)體將函數(shù)包裹一層,并將函數(shù)設(shè)為結(jié)構(gòu)體的第一個屬性,目的是利用結(jié)構(gòu)體地址就是首元素地址的特性

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

聲明makeInc結(jié)構(gòu)體,傳入makeIncrementer函數(shù),獲取VoidIntFunc類型指針ptr,再將ptr綁定為FunctionData<Box<Int>>類型,最終返回指針

var makeInc = VoidIntFun(f: makeIncrementer())

let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
ptr.initialize(to: makeInc)

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

print("incrementer內(nèi)嵌函數(shù)地址:\(ctx.ptr)")
print("runningTotal值:\(ctx.captureValue.pointee.value)")

//輸出以下內(nèi)容:
//incrementer內(nèi)嵌函數(shù)地址:0x0000000100005800
//runningTotal值:10

搜索符號:nmMach-O路徑】| grep【地址】。在終端搜索地址0000000100005800,記住沒有前面的0x

nm /Users/x/Library/Developer/Xcode/DerivedData/LGSwiftTest-ditbotpgxmdgkigjpenpcpgbyiwt/Build/Products/Debug/LGSwiftTest | grep 0000000100005800

輸出incrementer內(nèi)嵌函數(shù)的符號

0000000100005800 t _$s11LGSwiftTest15makeIncrementerSiycyF11incrementerL_SiyFTA

還原符號名稱:xcrun swift-demangle【符號】,在終端還原符號名稱,記住沒有前面的_$

xcrun swift-demangle s11LGSwiftTest15makeIncrementerSiycyF11incrementerL_SiyFTA

輸出incrementer內(nèi)嵌函數(shù)

$s11LGSwiftTest15makeIncrementerSiycyF11incrementerL_SiyFTA ---> partial apply forwarder for incrementer #1 () -> Swift.Int in LGSwiftTest.makeIncrementer() -> () -> Swift.Int

如果捕獲的是多個值,內(nèi)存布局是什么樣子的?
修改案例,讓makeIncrementer函數(shù)接收一個Int類型參數(shù)

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

將上述代碼生成IR文件:swiftc -emit-sil main.swift | xcrun swift-demangle

IR

通過lldb查看內(nèi)存

lldb

案例:捕獲多個值

捕獲makeIncrementer函數(shù)的入?yún)?code>amount,以及函數(shù)內(nèi)部的runningTotalvalstr三個不同數(shù)據(jù)類型變量

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

struct FuntionData<T>{
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<T>
}

struct Box<T1,T2,T3,T4> {
    var refCounted: HeapObject
    var valueBox: UnsafePointer<ValueBox<T1,T2,T3>>
    var value: T4
}

struct ValueBox<T1,T2,T3> {
    var obj1: ValueBoxObj<T1>
    var obj2: ValueBoxObj<T2>
    var obj3: ValueBoxObj<T3>
}

struct ValueBoxObj<T> {
    var refCounted: HeapObject
    var value: T
    var type: UnsafeRawPointer
}

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

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal: Int = 3
    var val: Double = 5.5
    var str: String = "Zang"
    
    func incrementer() -> Int {

        runningTotal += amount
        val += Double(amount)
        str += String(amount)

        return runningTotal
    }
    return incrementer
}

var makeInc = VoidIntFun(f: makeIncrementer(forIncrement: 10))

let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
ptr.initialize(to: makeInc)

let ctx = ptr.withMemoryRebound(to: FuntionData<Box<Int,Double,String,Int>>.self, capacity: 1) {
    $0.pointee
}

print("內(nèi)嵌函數(shù)地址:\(ctx.ptr)")
print("amount值:\(ctx.captureValue.pointee.value)")
print("runningTotal值:\(ctx.captureValue.pointee.valueBox.pointee.obj1.value)")
print("val值:\(ctx.captureValue.pointee.valueBox.pointee.obj2.value)")
print("str值:\(ctx.captureValue.pointee.valueBox.pointee.obj3.value)")

//輸出以下內(nèi)容:
//內(nèi)嵌函數(shù)地址:0x00000001000033f0
//amount值:10
//runningTotal值:3
//val值:5.5
//str值:Zang
  • 捕獲值的原理:在堆上開辟內(nèi)存空間,將捕獲的值放到這個內(nèi)存空間里
  • 修改捕獲值的時候,去堆上把變量拿出來,修改的就是堆空間里的值
  • 閉包是一個引用類型(地址傳遞),底層結(jié)構(gòu)是結(jié)構(gòu)體,包含函數(shù)地址和捕獲變量的值
函數(shù)也是引用類型
func makeIncrementer(inc: Int) -> Int {
   var runningTotal = 10
   return runningTotal + inc
}

var makeInc = makeIncrementer

將上述代碼生成IR文件:swiftc -emit-ir main.swift | xcrun swift-demangle

IR

struct FuntionData{
    var ptr: UnsafeRawPointer
    var captureValue: UnsafeRawPointer?
}

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

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

var makeInc = VoidIntFun(f: makeIncrementer)

let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
ptr.initialize(to: makeInc)

let ctx = ptr.withMemoryRebound(to: FuntionData.self, capacity: 1){$0.pointee}

print("函數(shù)地址:\(ctx.ptr)")
print("捕獲值:\(ctx.captureValue)")

//輸出以下內(nèi)容:
//函數(shù)地址:0x0000000100002070
//捕獲值:nil

搜索符號:nmMach-O路徑】| grep【地址】。在終端搜索地址0000000100002070,記住沒有前面的0x

nm /Users/x/Library/Developer/Xcode/DerivedData/LGSwiftTest-aqvqasiqlxeaiodejtwafdmetstw/Build/Products/Debug/LGSwiftTest | grep 0000000100002070

輸出makeIncrementer內(nèi)嵌函數(shù)的符號

0000000100002070 T _$s11LGSwiftTest15makeIncrementer3incS2i_tF

還原符號名稱:xcrun swift-demangle【符號】,在終端還原符號名稱,記住沒有前面的_$

xcrun swift-demangle s11LGSwiftTest15makeIncrementer3incS2i_tF

輸出makeIncrementer內(nèi)嵌函數(shù)

$s11LGSwiftTest15makeIncrementer3incS2i_tF ---> LGSwiftTest.makeIncrementer(inc: Swift.Int) -> Swift.Int

函數(shù)的本質(zhì):函數(shù)是引用類型,底層結(jié)構(gòu)是結(jié)構(gòu)體{函數(shù)地址,null}。結(jié)構(gòu)體只有函數(shù)地址,捕獲值為nil

逃逸閉包

逃逸閉包的定義:當(dāng)閉包作為?個實際參數(shù)傳遞給?個函數(shù)時,并且在函數(shù)返回后調(diào)?,我們就說這個閉包逃逸了。當(dāng)我們聲明?個接收閉包作為形式參數(shù)的函數(shù)時,可以在形式參數(shù)前寫@escaping來明確閉包是允許逃逸的

案例1:

將閉包賦值給變量complitionHandler,存儲后進行調(diào)用,如果?@escaping修飾閉包后,在逃逸閉包中應(yīng)該顯示引用self

class LGTeacher {
    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 t = LGTeacher()
t.doSomething()
t.complitionHandler?(10)

//輸出以下內(nèi)容:
//10

complitionHandlerLGTeachermakeIncrementer?法調(diào)?完成之后才會調(diào)?,此時閉包的?命周期?當(dāng)前?法?命周期更?,需要將閉包聲明成逃逸閉包。此案例沒有打印LGTeaher deinit,說明內(nèi)部存在循環(huán)引用

案例2:

使用DispatchQueue.global().asyncAfter延遲調(diào)用

class LGTeacher {

    func makeIncrementer(amount: Int,  handler: @escaping (Int) -> Void){
        var runningTotal = 0
        runningTotal += amount

        DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) {
            handler(runningTotal)
        }
    }

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

    deinit {
        print("LGTeaher deinit")
    }
}

var t = LGTeacher()
t.doSomething()

//輸出以下內(nèi)容:
//LGTeaher deinit
//10

makeIncrementer?法的執(zhí)?,不會等待閉包執(zhí)?完再執(zhí)?,?是直接返回。如果閉包的?命周期??法更?,需要將閉包聲明成逃逸閉包

上述兩個案例,如果不使用@escaping修飾,編譯報錯

編譯報錯

  • 逃逸閉包的使用,一般都是案例中存儲、延遲這兩種情況。逃逸閉包非常消耗資源,且逃逸閉包可能造成循環(huán)引用,需慎用
  • 逃逸閉包中應(yīng)該顯示引用self,表示這里有可能產(chǎn)生循環(huán)引用,起到提示作用,沒什么其他目的
非逃逸閉包

Swift 3.0之后,系統(tǒng)默認閉包為非逃逸閉包,參數(shù)被@nonescaping修飾,可以通過SIL代碼查看

func test(by: () -> Void) {
   by()
}

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

@nonescaping

  • 生命周期與函數(shù)一致,閉包只能在函數(shù)體內(nèi)執(zhí)行
  • 函數(shù)執(zhí)行完后,閉包表達式消失
  • 非逃逸閉包不會產(chǎn)生循環(huán)引用
  • 非逃逸閉包編譯器會做優(yōu)化,省略掉一些內(nèi)存管理調(diào)用
  • 非逃逸閉包上下文保存棧上,而不是堆上(官方文檔看到的,沒有驗證出來)
自動閉包
func debugOutPrint(_ condition: Bool , _ message: String){
    if condition {
        print("lg_debug:\(message)")
    }
}

debugOutPrint(true, "Application Error Occured")

//輸出以下內(nèi)容:
//lg_debug:Application Error Occured

上述代碼,只有在conditontrue的時候,才會打印傳入的錯誤信息,也就意味著false的時候當(dāng)前條件不會執(zhí)?

如果當(dāng)前的字符串,需要在某個業(yè)務(wù)邏輯功能中獲取,一般會這樣寫:

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

func doSomething() -> String{
    //do something and get error message
    return "NetWork Error Occured"
}

debugOutPrint(true, doSomething())

//輸出以下內(nèi)容:
//lg_debug:NetWork Error Occured

這種寫法會有?個問題,那就是當(dāng)前的conditon,?論是true還是falsedoSomething?法都會被執(zhí)?。如果doSomething?法內(nèi)部是耗時的任務(wù)操作,那么這?就造成了資源浪費

這時會想到把當(dāng)前參數(shù)修改成?個閉包,以此來解決資源浪費的問題

func debugOutPrint(_ condition: Bool , _ message: () -> String){
    if condition {
        print("lg_debug:\(message())")
    }
}

func doSomething() -> String{
    //do something and get error message
    return "NetWork Error Occured"
}

debugOutPrint(true, doSomething)

//輸出以下內(nèi)容:
//lg_debug:NetWork Error Occured

這樣的修改,雖然能滿?僅在符合條件的情況下才執(zhí)行doSomething?法,但新問題?隨之?來了。這?是?個閉包,如果輸出的信息是一個需要從外界傳?的String,那又該怎么辦呢?直接給方法傳入String是會編譯報錯的

編譯報錯

這種情況可以使?@autoclosure關(guān)鍵字,將當(dāng)前的表達式聲明成?個?動閉包。?動閉包不接收任何參數(shù),返回值是當(dāng)前內(nèi)部表達式的值。所以實際上我們傳?的String就是放在?個閉包表達式中,在調(diào)?的時候返回

func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
    if condition {
        print("lg_debug:\(message())")
    }
}

debugOutPrint(true, "Application Error Occured")

//輸出以下內(nèi)容:
//lg_debug:Application Error Occured

使用@autoclosure聲明?動閉包,相當(dāng)于將傳?的String使用閉包進行包裹,然后又在閉包中將String直接進行return。就像下面的代碼一樣:

{
   return "Application Error Occured"
}
  • ?動閉包不接收任何參數(shù)
  • 返回值是內(nèi)部表達式的值,在調(diào)?的時候返回

使用@autoclosure聲明?動閉包,也可以直接傳入函數(shù),且該函數(shù)在未滿足條件時,不會被執(zhí)行

func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
    
    print("condition當(dāng)前為:\(condition)")
    
    if condition {
        print("lg_debug:\(message())")
    }
    
    print("----------\n")
}

func doSomething() -> String{
    
    print("執(zhí)行了doSomething函數(shù)")
    return "NetWork Error Occured"
}

debugOutPrint(true, doSomething())
debugOutPrint(false, doSomething())

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

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

  • 前言:本篇文章的目的,在于記錄學(xué)習(xí)過程,敦促自己,方便查看。練習(xí)工具:Playground學(xué)習(xí)網(wǎng)站: swift5...
    麥穗0615閱讀 386評論 0 0
  • swift進階 學(xué)習(xí)大綱[http://www.lxweimin.com/p/0fc67b373540] 在 sw...
    markhetao閱讀 901評論 0 3
  • Swift 中的閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。類似于OC中的Block以及其他函數(shù)的匿名函數(shù)...
    喬克_叔叔閱讀 526評論 1 3
  • 86.復(fù)合 Cases 共享相同代碼塊的多個switch 分支 分支可以合并, 寫在分支后用逗號分開。如果任何模式...
    無灃閱讀 1,405評論 1 5
  • 前情提要 Swift的閉包和OC的Block是一回事,是一種特殊的函數(shù)-帶有自動變量的匿名函數(shù)。 分別從語法和原理...
    Jacob6666閱讀 424評論 0 0