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(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
兩個不同的常量可以同時引用一個閉包 。
關於閉包大致就這麼多。如有疑問可以相互交流學習 。希望共同進步!