天天看點

Swift程式設計八(閉包)閉包

案例代碼下載下傳

閉包

函數閉包可以從定義它們的上下文中捕獲和存儲對任何常量和變量的引用,Swift處理捕獲的所有記憶體管理。閉包包括以下三種形式之一:

  • 全局函數是具有名稱但不捕獲任何值的閉包。
  • 嵌套函數是具有名稱的閉包,可以從其封閉函數中捕獲值。
  • Closure表達式是一種未命名的閉包,用輕量級文法編寫,可以從上下文中捕獲值。

Swift的閉包表達式具有幹淨,清晰的風格,閉包的優勢包括:

  • 從上下文中推斷參數和傳回值類型
  • 單表達式閉包的隐式傳回
  • 速記參數名稱
  • 尾随閉包文法

閉包表達式

閉包表達式文法

Closure表達式文法具有以下一般形式:

{ (parameters) -> return type in
    statements
}
           

閉包的主體的開頭由in關鍵字引入。這個關鍵字表示閉包的參數和傳回類型的定義已經完成,閉包的主體開始:

var intArray = [7, 1, 4, 5, 9, 10, 6, 3, 8, 2]
intArray.sort(by: { (a, b) -> Bool in
    return a < b
})
print(intArray)
           

從上下文中推斷類型

閉包作為參數傳遞給方法,Swift可以推斷出它的參數類型以及它傳回的值的類型,是以參數必須是一個類型的函數。這意味着不需要将類型作為閉包表達式定義的一部分來編寫。因為可以推斷出所有類型,是以也可以省略傳回箭頭(->)和參數名稱周圍的括号:

intArray.sort(by: { a, b in
    return a < b
})
           

在将閉包作為内聯閉包表達式傳遞給函數或方法時,始終可以推斷出參數類型和傳回類型。是以,當閉包用作函數或方法參數時,永遠不需要以最完整的形式編寫内聯閉包。

單表達式閉包的隐式傳回

單表達式閉包可以省略return關鍵字來隐式傳回單個表達式的結果:

intArray.sort(by: { a, b in
    a < b
})
           

速記參數名稱

Swift自動提供内聯閉包速記參數名,它可以使用的名稱,指的是閉包的參數值$0,$1,$2,等等。

在閉包表達式中使用這些簡寫參數名稱,則可以從其定義中省略閉包的參數清單,并且将從期望的函數類型推斷縮寫參數名稱的數量和類型。in關鍵字也可以被省略,因為閉包表達是由完全其自身的:

intArray.sort(by: { $0 < $1 })
           

運算符方法

有一種更短的方式來編寫閉包表達式,Swift将小于運算符實作為具有兩個Int類型的參數的方法,并傳回Bool類型值。這與sorted(by:)方法所需要的參數類型是相符合的,是以可以簡單的使用一個小于符号,Swift将推斷出其實作:

intArray.sort(by: <)
           

尾随閉包

如果需要将閉包表達式作為函數的最後一個參數傳遞給函數,并且閉包表達式很長,則将其寫為尾随閉包可能很有用。在函數調用的括号之後寫入尾随閉包,即使它仍然是函數的參數。使用尾随閉包文法時,不要将閉包的參數标簽寫為函數調用的一部分。

intArray.sort() <
           

如果提供閉包表達式作為函數或方法的唯一參數,并且将該表達式作為尾随閉包提供,則在調用函數時,不需要在函數或方法的名稱後面寫一對括号():

intArray.sort { $0 < $1 }
           

捕捉值

閉包可以從定義它的周圍上下文中捕獲常量和變量。然後閉包可以引用并修改其體内的常量和變量的值,即使定義常量和變量的原始範圍不再存在。

捕獲值的最簡單形式的閉包是嵌套函數,寫在另一個函數體内。嵌套函數可以捕獲其外部函數的任何參數,還可以捕獲外部函數中定義的任何常量和變量。

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

作為優化,如果值在閉包内沒有發生改變,那麼閉包會捕獲并存儲值的副本。在閉包不需要變量時,Swift處理變量所涉及的記憶體管理。

注意: 如果為類執行個體的屬性配置設定閉包,并且閉包通過引用執行個體或其成員來捕獲該執行個體,則将在閉包和執行個體之間建立一個強引用循環。

let incrementByTen = makeIncrementer(forIncrement: 10)
let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementByTen())
print(incrementByTen())
print(incrementByTen())
print(incrementByTen())
print(incrementBySeven())
/*
列印結果:
10
20
30
40
7
*/
           

閉包是引用類型

無論何時将函數或閉包指派給常量或變量,實際上都是将該常量或變量設定為對函數或閉包的引用。也意味着如果為兩個不同的常量或變量配置設定閉包,那麼這兩個常量或變量都引用相同的閉包。

let otherIncrementByTen = incrementByTen
print(otherIncrementByTen())
print(otherIncrementByTen())
/*
列印結果:
50
60
*/
           

逃逸閉包

逃逸閉包是指當閉包作為參數傳遞給函數,但在函數傳回之後被調用的閉包。當聲明一個以閉包作為其參數之一的函數時,可以在參數的類型之前寫入@escaping,以訓示允許閉包逃逸。

閉包可以逃逸的一種方法是存儲在函數外部定義的變量中:

var completions = [() -> Void]()
func escapingClosureFunc(completion: @escaping () -> Void) {
    completions.append(completion)
}
           

逃逸閉包需要顯式的引用self:

func nonescapingClosureFunc(closure: () -> Void) {
    closure()
}
class SomeClass {
    var x = 0
    func someThing() {
        escapingClosureFunc {
            self.x = 100
        }
        nonescapingClosureFunc {
            x = 200
        }
    }
}
let instance = SomeClass()
instance.someThing()
print(instance.x)
completions.first!()
print(instance.x)
/*
列印結果:
200
100
*/
           

Autoclosures

autoclosure是自動建立被作為參數傳遞給函數的表達式的閉包。它不接受任何參數,當它被調用時,它傳回包含在表達式中的值。這種文法友善使通過編寫普通表達式而不是顯式閉包來省略函數參數周圍的大括号。

autoclosure允許延遲執行,因為在調用閉包之前,内部代碼不會運作。延遲執行對于具有副作用或計算成本高昂的代碼非常有用,因為它可以控制何時執行該代碼。

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
func autoclosureFunc(closure: @escaping @autoclosure () -> Int) -> () -> Int {
    return closure
}
print(array.count)
let closure = autoclosureFunc(closure: array.removeFirst())
print(array.count)
closure()
print(array.count)
/*
列印結果:
10
10
9
*/