麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > Swift > 正文

Swift教程之閉包詳解

2020-03-09 17:51:16
字體:
來源:轉載
供稿:網友
這篇文章主要介紹了Swift教程之閉包詳解,閉包可以在上下文的范圍內捕獲、存儲任何被定義的常量和變量引用,因這些常量和變量的封閉性,而命名為“閉包(Closures)”,需要的朋友可以參考下
 

閉包(Closures)是獨立的函數代碼塊,能在代碼中傳遞及使用。Swift中的閉包與C和Objective-C中的代碼塊及其它編程語言中的匿名函數相似。

閉包可以在上下文的范圍內捕獲、存儲任何被定義的常量和變量引用。因這些常量和變量的封閉性,而命名為“閉包(Closures)”。Swift能夠對所有你所能捕獲到的引用進行內存管理。

NOTE

假如你對“捕獲(capturing)”不熟悉,請不要擔心,具體可以參考Capturing Values(捕獲值)。

全局函數和嵌套函數已在 Functions(函數)中介紹過,實際上這些都是特殊的閉包函數
全局函數都是閉包,特點是有函數名但沒有捕獲任何值。
嵌套函數都是閉包,特點是有函數名,并且可以在它封閉的函數中捕獲值。
閉包表達式都是閉包,特點是沒有函數名,可以使用輕量的語法在它所圍繞的上下文中捕獲值。
Swift的閉包表達式有著干凈,清晰的風格,并常見情況下對于鼓勵簡短、整潔的語法做出優化。這些優化包括:
推理參數及返回值類型源自上下文
隱式返回源于單一表達式閉包
簡約參數名
尾隨閉包語法

1、閉包表達式

嵌套函數已經在Nested Functions(嵌套函數)中有所介紹,是種方便命名和定義自包含代碼塊的一種方式,然而,有時候在編寫簡短函數式的構造器時非常有用,它不需要完整的函數聲明及函數名,尤其是在你需要調用一個或多個參數的函數時。
閉包表達式是一種編寫內聯閉包的方式,它簡潔、緊湊。閉包表達式提供了數種語義優化,為的是以最簡單的形式編程而不需要大量的聲明或意圖。以下以同一個sort函數進行幾次改進,每次函數都更加簡潔,以此說明閉包表達式的優化。

Sort函數

Swift的標準函數庫提供了一個名為sort的函數,它通過基于輸出類型排序的閉包函數,給已知類型的數組數據的值排序。一旦完成排序工作,會返回一個同先前數組相同大小,相同數據類型,并且的新數組,并且這個數組的元素都在正確排好序的位置上。
The closure expression examples below use the sort function to sort an array of String values in reverse alphabetical order. Here's the initial array to be sorted:
以下的閉包表達式通過sort函數將String值按字母順序進行排序作說明,這是待排序的初始化數組。

 

復制代碼代碼如下:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

 

sort函數需要兩個參數:

一個已知值類型的數組
一個接收兩個參數的閉包函數,這兩個參數的數據類型都同于數組元素。并且
返回一個Bool表明是否第一個參數應排在第二個參數前或后。

這個例子是一組排序的字符串值,因此需要排序的封閉類型的函數(字符串,字符串)-> Bool。

構造排序閉包的一種方式是書寫一個符合其類型要求的普通函數:backwards,并將其返回值作為 sort 函數的第二個參數傳入:

 

復制代碼代碼如下:

func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = sort(names, backwards)
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

 

如果backwards函數參數 s1 大于 s2,則返回true值,表示在新的數組排序中 s1 應該出現在 s2 前。 字符中的 “大于” 表示 “按照字母順序后出現”。 這意味著字母 “B” 大于字母 “A”, 字符串 “Tom” 大于字符串 “Tim”。 其將進行字母逆序排序,”Barry” 將會排在 “Alex” 之后,以此類推。

但這是一個相當冗長的方式,本質上只是做了一個簡單的單表達式函數 :(a > b)。 下面的例子中,我們利用閉合表達式可以相比上面的例子更效率的構造一個內聯排序閉包。

閉包表達式語法

閉合表達式語法具有以下一般構造形式:

 

復制代碼代碼如下:
 
{ (parameters) -> return type in
statements
}

 

閉包表達式語法可以使用常量參數、變量參數和 inout 類型作為參數,但皆不可提供默認值。 如果你需要使用一個可變的參數,可將可變參數放在最后,元組類型也可以作為參數和返回值使用。

下面的例子展示了上面的 backwards 函數對應的閉包表達式構造函數代碼

 

復制代碼代碼如下:
 
reversed = sort(names, { (s1: String, s2: String) -> Bool in
return s1 > s2
})

需要注意的是聲明內聯閉包的參數和返回值類型與 backwards 函數類型聲明相同。 在這兩種方式中,都寫成了 (s1: String, s2: String) -> Bool類型。 然而在內聯閉包表達式中,函數和返回值類型都寫在大括號內,而不是大括號外。

 

閉包的函數體部分由關鍵字 in 引入。 該關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。

因為這個閉包的函數體非常簡約短所以完全可以將上面的backwards函數縮寫成一行連貫的代碼

 

復制代碼代碼如下:

reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )

 

可以看出 sort 函數的整體調用保持不變,還是一對圓括號包含兩個參數變成了內聯閉包形式、只不過第二個參數的值變成了。而其中一個參數現在變成了內聯閉包 (相比于 backwards 版本的代碼)。

根據上下文推斷類型

因為排序閉包是作為函數的參數進行傳入的,Swift可以推斷其參數和返回值的類型。 sort 期望第二個參數是類型為 (String, String) -> Bool 的函數,因此實際上 String, String 和 Bool 類型并不需要作為閉包表達式定義中的一部分。 因為所有的類型都可以被正確推斷,返回箭頭 (->) 和 圍繞在參數周圍的括號也可以被省略:

 

復制代碼代碼如下:
 
reversed = sort(names, { s1, s2 in return s1 > s2 } )

 

實際情況下,通過構造內聯閉包表達式的閉包作為函數的參數傳遞給函數時,都可以判斷出閉包的參數和返回值類型,這意味著您幾乎不需要利用完整格式構造任何內聯閉包。

同樣,如果你希望避免閱讀函數時可能存在的歧義, 你可以直接明確參數的類型。

這個排序函數例子,閉包的目的是很明確的,即排序被替換,而且對讀者來說可以安全的假設閉包可能會使用字符串值,因為它正協助一個字符串數組進行排序。

單行表達式閉包可以省略 return

單行表達式閉包可以通過隱藏 return 關鍵字來隱式返回單行表達式的結果,如上版本的例子可以改寫為:

 

復制代碼代碼如下:
 
reversed = sort(names, { s1, s2 in s1 > s2 } )

 

在這個例子中,sort 函數的第二個參數函數類型明確了閉包必須返回一個 Bool 類型值。 因為閉包函數體只包含了一個單一表達式 (s1 > s2),該表達式返回 Bool 類型值,因此這里沒有歧義,return關鍵字可以省略。

參數名簡寫

Swift 自動為內聯函數提供了參數名稱簡寫功能,可以直接通過 $0,$1,$2等名字來引用閉包的參數值。

如果在閉包表達式中使用參數名稱簡寫,可以在閉包參數列表中省略對其的定義,并且對應參數名稱簡寫的類型會通過函數類型進行推斷。 in 關鍵字也同樣可以被省略,因為此時閉包表達式完全由閉包函數體構成:

 

復制代碼代碼如下:
 
reversed = sort(names, { $0 > $1 } )

在這個例子中,$0 和 $1 表示閉包中第一個和第二個 String 類型的參數。

 

運算符函數
運算符函數實際上是一個更短的方式構造以上的表達式。

 

復制代碼代碼如下:
 
reversed = sort(names, >)

 

更多關于運算符表達式的內容請查看Operator Functions 。

2、Trailing 閉包

如果您需要將一個很長的閉包表達式作為最后一個參數傳遞給函數,可以使用 trailing 閉包來增強函數的可讀性。

Trailing 閉包是一個書寫在函數括號之外(之后)的閉包表達式,函數支持將其作為最后一個參數調用。

 

復制代碼代碼如下:

func someFunctionThatTakesAClosure(closure: () -> ()) {
// function body goes here
}
// here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure({
// closure's body goes here
})
// here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}

 

注意

如果函數只需要閉包表達式一個參數,當您使用 trailing 閉包時,您甚至可以把 () 省略掉。

在上例中作為 sort 函數參數的字符串排序閉包可以改寫為:

 

復制代碼代碼如下:
 
reversed = sort(names) { $0 > $1 }

 

當閉包非常長以至于不能在一行中進行書寫時,Trailing 閉包就變得非常有用。 舉例來說,Swift 的 Array 類型有一個 map 方法,其獲取一個閉包表達式作為其唯一參數。數組中的每一個元素調用一次該閉包函數,并返回該元素所映射的值(也可以是不同類型的值)。 具體的映射方式和返回值類型由閉包來指定。

當提供給數組閉包函數后,map 方法將返回一個新的數組,數組中包含了與原數組一一對應的映射后的值。

下例介紹了如何在 map 方法中使用 trailing 閉包將 Int 類型數組 [16,58,510] 轉換為包含對應 String 類型的數組 ["OneSix", "FiveEight", "FiveOneZero"]:

 

復制代碼代碼如下:
 
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

上面的代碼創建了整數數字到他們的英文名字之間映射字典。 同時定義了一個準備轉換為字符串的整型數組。

 

你現在可以通過傳遞一個 trailing 閉包給 numbers 的 map 方法來創建對應的字符串版本數組。需要注意的時調用 numbers.map不需要在 map 后面包含任何括號,因為只需要傳遞閉包表達式這一個參數,并且該閉包表達式參數通過 trailing 方式進行撰寫:

 

復制代碼代碼如下:

let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

map 在數組中為每一個元素調用了閉包表達式。您不需要指定閉包的輸入參數 number 的類型,因為可以通過要映射的數組類型進行推斷。

 

閉包 number 參數被聲明為一個變量參數 (變量的具體描述請參看Constant and Variable Parameters),因此可以在閉包函數體內對其進行修改。閉包表達式制定了返回值類型為 String,以表明存儲映射值的新數組類型為 String。

閉包表達式在每次被調用的時候創建了一個字符串并返回。其使用求余運算符 (number % 10) 計算最后一位數字并利用digitNames 字典獲取所映射的字符串。

注意:

字典 digitNames 下標后跟著一個嘆號 (!),因為字典下標返回一個可選值 (optional value),表明即使該 key不存在也不會查找失敗。 在上例中,它保證了 number % 10 可以總是作為一個 digitNames 字典的有效下標 key。 因此嘆號可以用于強展開 (force-unwrap) 存儲在可選下標項中的 String 類型值。

從 digitNames 字典中獲取的字符串被添加到輸出的前部,逆序建立了一個字符串版本的數字。 (在表達式 number % 10中,如果number為16,則返回6,58返回8,510返回0)。

number 變量之后除以10。 因為其是整數,在計算過程中未除盡部分被忽略。 因此 16變成了1,58變成了5,510變成了51。

整個過程重復進行,直到 number /= 10 為0,這時閉包會將字符串輸出,而map函數則會將字符串添加到所映射的數組中。

上例中 trailing 閉包語法在函數后整潔封裝了具體的閉包功能,而不再需要將整個閉包包裹在 map 函數的括號內。

3、獲取值

閉包可以在其定義的范圍內捕捉(引用/得到)常量和變量,閉包可以引用和修改這些值,即使定義的常量和變量已經不復存在了依然可以修改和引用。牛逼吧、

在Swift中最簡單形式是一個嵌套函數,寫在另一個函數的方法里面。嵌套函數可以捕獲任何外部函數的參數,也可以捕獲任何常量和變量在外部函數的定義。

看下面這個例子,一個函數方法為makeIncrementor、這是一個嵌套函數,在這個函數體內嵌套了另一個函數方法:incrementor,在這個incrementor函數體內有兩個參數: runningTotal和amount,實際運作時傳進所需的兩個參數后,incrementor函數每次被調用時都會返回一個runningTotal值提供給外部的makeIncrementor使用:

復制代碼代碼如下:

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

 

而函數makeincrementor的返回類型值我們可以通過函數名后面的()-> int得知返回的是一個Int類型的值。如需想學習了解更多地函數返回類型,可以參考: Function Types as Return Types.(超鏈接跳轉)

我們可以看見makeincrementor這個函數體內首先定義了一個整型變量:runningtotal,初始值為 0 ,而incrementor()函數最終運行的出來的返回值會賦值給這個整型變量。

makeincrementor函數()中向外部拋出了一個forIncrement參數供外部穿參進來、一旦有值進入函數體內會被函數實例化替代為amount,而amount會被傳遞進內嵌的incrementor函數體中與整型常量runningTotal相加得到一個新的runningTotal并返回。而我們這個主函數要返回的值是Int類型,runningTotal直接作為最終值被返回出去、makeincrementor函數()執行完畢。

makeincrementor函數()在其內部又定義了一個新的函數體incrementor,作用就是將外部傳遞過來的值amount 傳進incrementor函數中與整形常量runningTotal相加得到一個新的runningTotal,

單獨的看incrementor函數、你會發現這個函數不尋常:

復制代碼代碼如下:

func incrementor() -> Int {
runningTotal += amount
return runningTotal
}

 

因為incrementor函數沒有任何的參數,但是在它的函數方法體內卻指向runningTotal和amount,顯而易見、這是incrementor函數獲取了外部函數的值amount,incrementor不能去修改它但是卻可以和體內的runningTotal相加得出新的runningTotal值返回出去。

不過,由于runningtotal每次被調用時都會相加改變一次實際值,相應地incrementor函數被調用時會去加載最新的runningtotal值,而不再是第一次初始化的0.并且需要保證每次runningTotal的值在makeIncrementor函數體內不會丟失直到函數完全加載完畢。要能確保在函數體內下一次引用時上一次的值依然還在。

注意
Swift中需要明確知道什么時候該引用什么時候該賦值,在incrementor函數中你不需要注解amount 和runningTotal。Swift還負責處理當函數不在需要runningTotal的時候,內存應該如何去管理。

這里有一個例子makeIncrementor函數:

 

復制代碼代碼如下:

let incrementByTen = makeIncrementor(forIncrement: 10)

 

4、引用類型閉包

在上面的例子中,incrementBySeven和incrementByTen是常量,但是這些常量在閉包的狀態下依然可以被修改。為何?很簡單,因為函數和閉包是引用類型。
當你指定一個函數或一個閉包常量/變量時、實際上是在設置該常量或變量是否為一個引用函數。在上面的例子中,它是閉合的選擇,incrementByTen指的是恒定的,而不是封閉件本身的內容。
這也意味著,如果你分配一個封閉兩種不同的常量或變量,這兩個常量或變量將引用同一個閉包:

 

復制代碼代碼如下:
 
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50


注:相關教程知識閱讀請移步到swift教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 国产成人高潮免费观看精品 | 成人影片在线免费观看 | 久久精国 | 91av视频大全 | 综合网日日天干夜夜久久 | 国产精品a一 | 精品一区二区三区免费 | 国产成人自拍小视频 | 国产va在线观看 | 美女黄污视频 | 欧美性受xxxx白人性爽 | 日韩视频一二三 | 成人在线免费观看视频 | 成人小视频在线播放 | 亚洲成人欧美在线 | 黄色大片高清 | 黄在线看 | 91看片成人 | 综合精品视频 | a视频网站 | 中文字幕爱爱视频 | 福利在线小视频 | 亚洲免费看片网站 | 久久69精品久久久久久国产越南 | 麻豆一二区 | 黄色片观看| 国产高潮国产高潮久久久91 | 成人毛片100部免费观看 | 久久99深爱久久99精品 | 黄色一级片免费观看 | 久色伊人| 成人黄色小视频在线观看 | 性生活视频软件 | 久久久久久久久成人 | 久久精品一区二区三区国产主播 | 精品国产一区二区三区久久久蜜 | 草草视频免费观看 | 激情小说激情图片激情电影 | 3级毛片 | 免费啪视频在线观看 | 成年人黄色免费电影 |