05-Swift閉包(Closures)

* 閉包 是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。swift中的閉包和Objective-C中的代碼塊(block)以及其他一些編程語言中的匿名函數(shù)類型。
?
閉包可以捕獲和存儲(chǔ)其所在上下文中任意常量和變量的引用。這所謂的閉合并包裹這些常量和變量,即是 閉包 *。Swift中會(huì)自動(dòng)處理在捕獲過程中涉及到的所有內(nèi)存操作。

函數(shù)分全局和嵌套函數(shù),而函數(shù)其實(shí)是特殊的閉包,閉包采取如下三種形式之一:
  • 全局函數(shù)是一個(gè)有名字,但不會(huì)捕獲任何值的閉包;
  • 嵌套函數(shù)是一個(gè)有名字,并可以捕獲其封閉函數(shù)區(qū)域內(nèi)的值的閉包;
  • 閉包表達(dá)式是一個(gè)利用輕量級(jí)語法所寫的,可以捕獲其上下文中變量或常量值的匿名閉包;
    (即全局函數(shù)、嵌套函數(shù)也都是閉包!)
Swift的閉包表達(dá)式的擁有簡潔風(fēng)格,并鼓勵(lì)在一下常見場景中進(jìn)行語法優(yōu)化:
  • 利用上下文推斷參數(shù)和返回值類型;
  • 隱式返回單表達(dá)式閉包,即單表達(dá)式閉包可以省略return關(guān)鍵字;
  • 參數(shù)名稱縮寫;
  • 尾隨閉包語法;




一、閉包表達(dá)式


* 嵌套函數(shù) 是一個(gè)在復(fù)雜函數(shù)中方便進(jìn)行命名和定義自包含代碼模塊的方式。 閉包表達(dá)式 *是一種利用簡潔語法構(gòu)建內(nèi)聯(lián)閉包的方式。

  • sort方法。Swift標(biāo)準(zhǔn)庫中提供了名為sort的方法,會(huì)根據(jù)所提供的用于排序的閉包函數(shù),將已知類型數(shù)組中的值進(jìn)行排序。排序完成后,sort(_:)方法會(huì)返回一個(gè)與原數(shù)組大小相同,包含同類型且元素已是排序好的新數(shù)組,而原數(shù)組不會(huì)被sort(_:)方法修改。

sort方法的使用:sort(_:)方法接受一個(gè)閉包,該閉包需要傳入與數(shù)組元素類型相同的兩個(gè)值,并返回一個(gè)布爾類型的值,這是用于表明當(dāng)排序結(jié)束后傳入的第一個(gè)參數(shù)是在第二個(gè)參數(shù)的前面還是后面。如果第一個(gè)參數(shù)值出現(xiàn)在第二參數(shù)值前面,排序閉包需要返回true,否則返回false

// 數(shù)組對(duì)應(yīng): 張三、李四、王五、趙六、田七
let names = ["zhansan","lisi","wangwu","zhaoliu","tianqi"];
// 排序方法
func mySort(str1:String, str2:String) -> Bool {
    // 即str1是在str2的前面,返回true
    return str1 > str2;
}
// 調(diào)用sort方法排序
var sortNames = names.sort(mySort);
print("排序前:\(names)");
print("排序后:\(sortNames)");
輸出結(jié)果:
排序前:["zhansan", "lisi", "wangwu", "zhaoliu", "tianqi"]
排序后:["zhaoliu", "zhansan", "wangwu", "tianqi", "lisi"]
  • 閉包表達(dá)式語法。在這種可以使用常量、變量和inout類型作為參數(shù),不能提供默認(rèn)值。也可以在參數(shù)列表的最后使用可變參數(shù)。另外,元組也可以作為參數(shù)和返回值:
// 上面mySort(_:_:)函數(shù)可以寫成閉包表達(dá)式
let names = ["zhansan","lisi","wangwu","zhaoliu","tianqi"];
let sortNames = names.sort({ (str1:String, str2:String) -> Bool in
    return str1 > str2;
});
// 代碼簡單,改寫為一行
// let sortNames = names.sort({ (str1:String, str2:String) -> Bool in return str1 > str2; });
print("排序前:\(names)");
print("排序后:\(sortNames)");
輸出結(jié)果:
排序前:["zhansan", "lisi", "wangwu", "zhaoliu", "tianqi"]
排序后:["zhaoliu", "zhansan", "wangwu", "tianqi", "lisi"]

閉包表達(dá)式語法一般形式
{ (parameters) -> returnType in
statements
}
// 注意1: 參數(shù)和返回值類型都是在大括號(hào)內(nèi),而不是大括號(hào)外;
// 注意2: 閉包中代碼部分由關(guān)鍵字in引入,即是in表示閉包的參數(shù)和返回值類型定義已經(jīng)完成,閉包的代碼模塊即將開始;

  • 根據(jù)上下文推斷類型。在上面排序中使用到sort(_:)方法,而該方法其參數(shù)必須是(String, String) -> Bool類型,也就意味(String, String)Bool并不需要作為閉包表達(dá)式定義的一部分。因?yàn)轭愋投伎梢员徽_推斷:
// 即省略參數(shù)類型和返回值類型
let sortNames = names.sort({ str1, str2 in return str1 > str2 });
  • 單表達(dá)式閉包隱式返回。即單行表達(dá)式閉包可以通過省略return關(guān)鍵字來隱式返回單行表達(dá)式的結(jié)果:
// 即省略return關(guān)鍵字
let sortNames = names.sort({ str1, str2 in str1 > str2 });
  • 參數(shù)名稱縮寫。Swift自動(dòng)為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能,你可以用$0$1$2來順序調(diào)用閉包的參數(shù),以此類推。另外,類型是可以通過上下文推斷,所以in關(guān)鍵字也同樣可以省略:
// 即參數(shù)縮寫
let sortNames = names.sort({ $0 > $1});
  • 運(yùn)算符函數(shù)。swift的String類型定義了關(guān)于大于號(hào)>的字符串實(shí)現(xiàn),其作為函數(shù)接收兩個(gè)String類型的參數(shù)并返回Bool類型的值,因此上面排序就還可以寫的更簡短(簡短的讓你抓狂,別懷疑,絕對(duì)是可以運(yùn)行的,也是絕對(duì)沒問題的):
// 運(yùn)算符函數(shù)的運(yùn)用
let sortNames = names.sort(>);
// 注: >即從大到小,若是換為<即是從小到大


二、尾隨閉包


  • 若閉包表達(dá)式很長作為最后一個(gè)參數(shù)傳遞給參數(shù),可以使用* 尾隨閉包 *來增強(qiáng)函數(shù)的可讀性:
// 參數(shù)縮寫
//let sortNames = names.sort({ $0 > $1});
// 尾隨閉包
//let sortNames = names.sort(){ $0 > $1 };
// 尾隨閉包也可以將()省略
let sortNames = names.sort { $0 > $1 };
  • 當(dāng)閉包非常長以至于不能在一行中進(jìn)行書寫時(shí),尾隨閉包就非常有用:
// 將Int類型數(shù)組[16, 58, 510]轉(zhuǎn)為包含對(duì)應(yīng)String類型值的數(shù)組["OneSix", "FiveEight", "FiveOneZero"]
// 數(shù)值位和英文版名字相映射的字典
let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
// 需要轉(zhuǎn)換的數(shù)組
let  numbers = [16, 58, 510];
// 省略括號(hào)、閉包尾隨
let str = numbers.map {
    //  參數(shù)為number,返回值類型String
    (var number) -> String in
    // 具體代碼區(qū)域
    var output = "";
    while number > 0 {  // 取出對(duì)應(yīng)位數(shù)的數(shù)值
        // 從個(gè)位開始,并找到對(duì)應(yīng)英文,最后拼接
        output = digitNames[number % 10]! + output;
        // 從個(gè)位開始截去
        number = number / 10;
    }
    return output;
}
print(numbers);
print(str);
輸出結(jié)果:
[16, 58, 510]
["OneSix", "FiveEight", "FiveOneZero"]

Array類型中map(_:)方法: 獲取一個(gè)閉包表達(dá)式作為其唯一參數(shù)。該閉包會(huì)為數(shù)組中的每一個(gè)元素調(diào)用一次,并返回該元素所映射的值,而具體的映射方式和返回值類型由閉包來指定。


三、捕獲值


閉包可以在其被定義的上下文中* 捕獲 常量或變量。即定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包的代碼區(qū)域中引用和修改這些值。
?swift中,可以捕獲值的閉包其最簡單形式就是
嵌套函數(shù) ,即是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù), 嵌套函數(shù) *可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。

/** 實(shí)現(xiàn)計(jì)步功能*/
// 返回類型: (Int) -> Int
func makeRunningTotal() -> (Int) -> Int {
    var runningTotal = 0
    
    // 嵌套函數(shù),特殊閉包,參數(shù)amount是一個(gè)增量
    func addFunc(amount:Int) -> Int {
        // 捕獲runningTotal
        runningTotal += amount
        return runningTotal
    }
    
    // 注意: 不要寫成addFunc(),這是函數(shù)調(diào)用,而不是返回函數(shù)
    // 即makeRunningTotal將addFunc作為閉包返回
    return addFunc;
}
// 李明
let liming = makeRunningTotal();
print("李明-總步數(shù): \(liming(10))");
print("李明-總步數(shù): \(liming(20))");
print("李明-總步數(shù): \(liming(30))");
// 張三
let zhagnsan = makeRunningTotal();
print("張三-總步數(shù): \(zhagnsan(50))");
print("張三-總步數(shù): \(zhagnsan(100))");
print("張三-總步數(shù): \(zhagnsan(200))");
輸出結(jié)果:
李明-總步數(shù): 10
李明-總步數(shù): 30
李明-總步數(shù): 60
張三-總步數(shù): 50
張三-總步數(shù): 150
張三-總步數(shù): 350

單獨(dú)看嵌套函數(shù),會(huì)發(fā)現(xiàn)函數(shù)體中使用的變量runningTotal不是在函數(shù)體內(nèi)部定義的;addFunc()函數(shù)就是外圍函數(shù)中捕獲了runningTotal的引用;捕獲引用保證runningTotal在調(diào)用完addFunc()后不會(huì)消失,并保證在下一次執(zhí)行addFunc()函數(shù)的時(shí)候,runningTotal依舊存在;
func addFunc(amount:Int) -> Int {
runningTotal += amount;
return runningTotal;
}


四、閉包是引用類型


閉包是* 引用類型 *,在上述例子中,limingRunningTotalzhagnsanRunningTotal是常量,但這些常量是指向閉包,仍然還是可以增加其捕獲的變量值。
?無論是將閉包賦值給一個(gè)常量還是變量,實(shí)際都是將常量或變量的值設(shè)置為對(duì)用閉包的引用。


五、非逃逸閉包


當(dāng)一個(gè)閉包作為參數(shù)傳入到一個(gè)函數(shù)中,但這個(gè)閉包在函數(shù)返回之后才被執(zhí)行,該閉包稱為從函數(shù)中* 逃逸 *。
?當(dāng)你在定義函數(shù),其參數(shù)是閉包,在參數(shù)之前使用@noescape標(biāo)注,即表明該閉包不允許"逃逸"出這個(gè)函數(shù)。而這個(gè)@noescape閉包標(biāo)注,就是是告訴編譯器這個(gè)閉包的生命周期是在它所在函數(shù)體中的。
?而在默認(rèn)情況,即沒有加上@noescape標(biāo)注的,閉包是可以"逃逸"出函數(shù)的。

  • 非逃逸閉包:
/** 下載數(shù)據(jù)的函數(shù) - 非逃逸閉包
 參數(shù)接收一個(gè)閉包
 @noescape即表明該閉包是屬于"非逃逸閉包"
 */
func downloadData1 (@noescape closure:(Void) -> Void) {
    // 閉包c(diǎn)losure的調(diào)用,就是該閉包c(diǎn)losure只有在downloadData1函數(shù)中執(zhí)行調(diào)用
    print("即將開始下載...");
    closure();
    print("已經(jīng)開始下載...");
}
// 調(diào)用下載函數(shù)
downloadData1() {
    print("正在下載QQ...")
    sleep(2);   // 延時(shí)操作,延時(shí)2秒,用于演示正在下載QQ
};
/** 效果
先打印"即將開始下載...",
再打印"正在下載QQ...",
2秒過后,
最后才打印"已經(jīng)開始下載..."
即表示閉包是在downloadData1函數(shù)中執(zhí)行的;
*/
輸出結(jié)果:
即將開始下載...
正在下載QQ...
已經(jīng)開始下載...
  • 逃逸閉包:
// 具體操作處理者
// 處理者這里是一個(gè)數(shù)組,即要處理的事件數(shù)組
var downLoadHandler:[(Void)->(Void)] = [];
/** 下載數(shù)據(jù)的函數(shù) - 逃逸閉包
 參數(shù)接收一個(gè)閉包
 沒有@noescape,即表明該閉包是屬于"逃逸閉包"
 */
func downloadData2 (closure:(Void) -> Void) {
    // 將具體操作添加到處理者,而不需要管什么時(shí)候處理
    // 只添加,但閉包是沒有執(zhí)行的!!!
    // 閉包c(diǎn)losure不是在downloadData2中執(zhí)行,即是"逃逸閉包"
    print("即將添加到處理者");
    downLoadHandler.append(closure);  
    print("已經(jīng)添加到處理者");
}
// 添加下載QQ操作
downloadData2() {
    print("正在下載QQ...");
    sleep(2);   // 延時(shí)操作,用于演示正在下載QQ
};
// 添加下載xcode操作
downloadData2 {
    print("正在下載xcode...");
    sleep(2); // 延時(shí)操作,用于演示正在下載xcode
}
// 處理者downLoadHandler,處理事件
for (index,closure) in downLoadHandler.enumerate() {
    // 具體處理事件
    print("正在處理事件:\(index)");
    closure();
    print("處理完成事件:\(index)");
}
輸出結(jié)果:
即將添加到處理者  
已經(jīng)添加到處理者  // 即是添加"下載QQ"閉包
即將添加到處理者
已經(jīng)添加到處理者  // 即是添加"下載xcode"閉包
正在處理事件:0    // 即是"下載QQ"閉包的調(diào)用
正在下載QQ...
處理完成事件:0
正在處理事件:1    // 即是"下載xcode"閉包的調(diào)用
正在下載xcode...
處理完成事件:1

注: 關(guān)于* 逃逸閉包 ,更多都會(huì)用于異步操作相關(guān)的封裝。即在異步操作開始后就立即返回,而 逃逸閉包 *是在異步操作結(jié)束之后才會(huì)被調(diào)用。


六、自動(dòng)閉包


* 自動(dòng)閉包 *是一種自動(dòng)創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式,而這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用后,會(huì)返回包裝在其中表達(dá)式的字。

  • 自動(dòng)閉包語法可以將一段具體操作封裝成一個(gè)變量或常量,方便使用操作:
// 將問候操作以閉包形式賦值給一個(gè)常量
let speakClosures = {
    print("hello world!");
    print("hello swift!");
}
// 當(dāng)在需要問候的時(shí)候,只需要調(diào)用speakClosures即可
speakClosures();
// 作為參數(shù)傳遞
func testFunc(closures:()->()) {
    // 調(diào)用
    closures();
}
testFunc(speakClosures);
輸出結(jié)果:
hello world!
hello swift!
hello world!
hello swift!


注:xcode7.3環(huán)境

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

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

  • 86.復(fù)合 Cases 共享相同代碼塊的多個(gè)switch 分支 分支可以合并, 寫在分支后用逗號(hào)分開。如果任何模式...
    無灃閱讀 1,404評(píng)論 1 5
  • 閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代...
    窮人家的孩紙閱讀 1,726評(píng)論 1 5
  • Swift 中的閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。類似于OC中的Block以及其他函數(shù)的匿名函數(shù)...
    喬克_叔叔閱讀 526評(píng)論 1 3
  • 閉包是功能性自包含模塊,可以在代碼中被傳遞和使用。Swift中的閉包與 C 和 Objective-C中的 blo...
    AirZilong閱讀 353評(píng)論 0 2
  • 綠玉呵紅艷, 名喚美人蕉。 斑斕勝百花, 凄清比蘭草。 細(xì)雨只增媚, 烈日總不凋。 微風(fēng)裊裊過, 嫣然對(duì)我笑。 注...
    詩夢(mèng)劍客閱讀 246評(píng)論 0 0