1. 程式人生 > >Swift詳解之四-------媽媽再也不用擔心我的閉包了

Swift詳解之四-------媽媽再也不用擔心我的閉包了

媽媽再也不用擔心我的閉包了

注:本文為作者自己總結,過於基礎的就不再贅述 ,都是親自測試的結果。如有錯誤或者遺漏的地方,歡迎指正,一起學習。

swift中閉包是一個很強大的東西,閉包是自包含的函式程式碼塊,可以在程式碼中被傳遞和使用。跟C 和 Objective-C 中的程式碼塊(blocks)很相似 。這個大家必須掌握!必須掌握!必須掌握!重要的事情要說三遍

閉包可以捕獲和儲存其所在上下文中任意常量和變數的引用。 這就是所謂的閉合幷包裹著這些常量和變數,俗稱閉包。下面我們就來攻克它!

1、閉包函式

官方在講解閉包函式的時候一般都是使用一個sort() 的排序方法,我們來看看這個例子:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}

這裡我們定義了一個String 型別的陣列,然後定義了一個function,接受兩個 接收兩個String 型別的引數,返回bool

然後我們來了解下sort() 我們這裡對這個陣列進行排序 按照我們定義的方法的規則,var reversed = names.sort(backwards)
這個sort() 在swift 2.0加入陣列,成為陣列的成員方法可以直接呼叫 ,這裡傳入與陣列型別相同的兩個值 ,並返回bool

,如果返回true 就把第一個引數放在第二個前面 (也就是降序),反之你懂的

所以 ,我們這個函式的意思就是如果第一個比第二個引數大就返回true(s1>s2) ,所以是一個降序的排列 ,這裡得到的結果是 :[Ewa, Daniella, Chris, Barry, Alex]

大家看著官方的sort() 也看不到具體的實現,所以這塊有可能不是很清楚 ,那麼我們自己寫一個排序 ,也傳入我們這個函式。


func mySort (var arr:[String] , sortStr:(String,String)->Bool)->[String]
{
    if
(arr.count == 0){ return arr; } let count = arr.count var temp = "" for i in 0..<count { for j in i+1..<count { if(!sortStr(arr[i],arr[j])) { temp = arr[i] arr[i] = arr[j] arr[j] = temp } } } return arr; }

者其實就是一個簡單的氣泡排序 ,只不過把規則交給呼叫者 。
呼叫方法 mySort(names, sortStr: backwards) 這裡傳入的是一個數組和一個函式型別 。得到結果 :[Ewa, Daniella, Chris, Barry, Alex] 跟官方的sort一樣的 。

下面的例項我們就用自己的sort 來講,程式碼都在這裡。大家可以看得明白 。

這裡需要補充一個知識點。

區間運算 :0...n  表示0-n的閉合區間  0..<n 表示一個包含0 不包含n半開半閉

2、閉包表示式語法

然而這是一個相當冗長的方式,本質上只是寫了一個單表示式函式 (a > b),但是我們還要寫一個函式 ? 當然不需要,下面我們用閉合表示式語法可以更好的構造一個內聯排序閉包

閉包表示式語法有如下一般形式:
{ (parameters) -> returnType in
        statements
}

閉包表示式語法可以使用常量、變數和inout型別作為引數,不提供預設值。 也可以在引數列表的最後使用可變引數。 元組也可以作為引數和返回值。

let arr1 = mySort( (names) , sortStr: { (s1:String,s2:String) -> Bool in
    return s1>s2
})

這裡我們把上面傳入函式的地方,我們傳入了一個閉包。這裡就不做過多解釋。完全按照上面的語法型別

  • 根據上下文推斷型別

因為我們寫的mySort 第二個引數是型別為(String, String) -> Bool的函式,因此實際上String,String和Bool型別並不需要作為閉包表示式定義中的一部分。 因為所有的型別都可以被正確推斷,返回箭頭 (->) 和圍繞在引數周圍的括號也可以被省略:

所以我們的可以這樣寫 :

let arr2 = mySort( (names) , sortStr: { s1,s2 in return s1>s2 })

看到沒,智慧的swift幫我們把閉包縮短了很多

  • 單表示式閉包隱式返回

單行表示式閉包可以通過隱藏return關鍵字來隱式返回單行表示式的結果,我們這裡是單行表示式 。
注:不要方便的用慣了多行表示式別也給省略了

let arr3 = mySort( (names) , sortStr: { s1,s2 in s1>s2 })

我們的程式碼又短了很多

  • 引數名稱縮寫
    Swift 自動為行內函數提供了引數名稱縮寫功能,您可以直接通過0,1,$2來順序呼叫閉包的引數。如果您在閉包表示式中使用引數名稱縮寫,您可以在閉包引數列表中省略對其的定義,並且對應引數名稱縮寫的型別會通過函式型別進行推斷。 in關鍵字也同樣可以被省略

這時候我們的閉包就變成了下面這樣

let arr4 = mySort( (names) , sortStr: { $0>$1 })

哇!太厲害了,swift太強大了,你以為這是終極目標了,錯了 還有更厲害的

  • 運算子函式

Swift 的String型別定義了關於大於號 (>) 的字串實現,其作為一個函式接受兩個String型別的引數並返回Bool型別的值
您可以簡單地傳遞一個大於號,Swift可以自動推斷出您想使用大於號的字串函式實現
最終我們的閉包變成了這樣

let arr5 = mySort( (names) , sortStr: > )
print(arr5) //[Ewa, Daniella, Chris, Barry, Alex]
  • 尾隨閉包

尾隨閉包是一個書寫在函式括號之後的閉包表示式,函式支援將其作為最後一個引數呼叫。

let arr6 = mySort(names) { $0>$1 }

當閉包非常長以至於不能在一行中進行書寫時,尾隨閉包變得非常有用。這中方式也是我們經常時候的方式

3、捕獲值

閉包可以在其定義的上下文中捕獲常量或變數。 即使定義這些常量和變數的原域已經不存在,閉包仍然可以在閉包函式體內引用和修改這些值。

看到這段話是不是很暈呀,哈哈我們來看一個例項你就理解了 。

func makeRunStep(step:Int)->()->Int
{
    var total = 0;

//    func run()->Int {
//        total+=step ;
//        return total;
//    }
    return {()-> Int in  total+=step ;
                         return total; }
}

這裡我們定義了一個函式,傳入一個Int ,返回()->Int的函式型別 。在函式中我們定義一個統計總數的變數total ,直接返回一個閉包 ,在閉包中使用傳入的引數和變數total ,我們這裡也可以用我註釋的那段,寫一個內嵌函式 ,然後返回這個內嵌函式 。

let ten = makeRunStep(10);

這裡傳入一個10 ,並把返回的函式型別賦值給一個常量ten 。
執行該方法ten() 得到結果 10

當我們再去執行ten() 的時候,由於沒有修改step,這裡total實際上捕獲並存儲了該變數的一個副本,而該副本隨著閉包一同被儲存在ten這個變數中,因為每次呼叫該函式的時候都會修改total的值,閉包捕獲了當前total變數的引用,而不是僅僅複製該變數的初始值。捕獲一個引用保證了當makeRunStep結束時候並不會消失,也保證了當下一次執行閉包時,total可以繼續增加

所以得到的結果是 :20

let ten = makeRunStep(10);
ten()   //10
ten()   //20
ten()   //30
ten()   //40
let ten2 = makeRunStep(10);
ten2() //10

如果重新呼叫makeRunStep(7) 則重新開始計數

let seven = makeRunStep(7);
seven() //7
seven() //14
seven() //21
seven() //28

大家看到這幾組資料大概明白什麼意思了吧, 其實真正的原因是閉包是一個引用型別的 ,let ten = makeRunStep(10); 我們這裡雖然用一個常量接收了這個閉包,但是直接接受了它的引用,並不是閉包本身 。

let ten = makeRunStep(10);
ten()   //10
ten()   //20
ten()   //30
ten()   //40
let ten1 = ten
ten1() //50
print(ten1()) //60

兩個不同的常量可以同時引用一個閉包 。

關於閉包大致就這麼多。如有疑問可以相互交流學習 。希望共同進步!