函數(shù)
當(dāng)你定義一個(gè)函數(shù)時(shí),你可以定義一個(gè)或多個(gè)有名字和類型的值禽绪,作為函數(shù)的輸入,稱為參數(shù)洪规,也可以定義某種類型的值作為函數(shù)執(zhí)行結(jié)束時(shí)的輸出印屁,稱為返回類型。
每個(gè)函數(shù)有個(gè)函數(shù)名斩例,用來(lái)描述函數(shù)執(zhí)行的任務(wù)雄人。要使用一個(gè)函數(shù)時(shí),用函數(shù)名來(lái)調(diào)用這個(gè)函數(shù)念赶,并傳給它匹配的輸入值實(shí)參
础钠。函數(shù)的實(shí)參必須與函數(shù)參數(shù)表里參數(shù)的順序一致。
函數(shù)參數(shù)與返回值
函數(shù)的定義以func
作為前綴叉谜,后接方法名旗吁,后跟一對(duì)括號(hào)定義參數(shù),參數(shù)用,
隔開(kāi)停局;指定函數(shù)返回類型時(shí)很钓,用返回箭頭->
后跟返回類型的名稱的方式來(lái)表示。
無(wú)參數(shù)函數(shù)
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// 打印 "hello, world"
多參數(shù)函數(shù)
func add(num1: Int, num2: Int) -> String {
return "sum is \(num1+num2)"
}
print(add(num1: 3, num2: 5))
// 打印 "sum is 8"
無(wú)返回值函數(shù)
func say(message: String) {
print("say \(message)")
}
say(message: "hello")
// 打印 say hello
嚴(yán)格上來(lái)說(shuō)董栽,雖然沒(méi)有返回值被定義码倦,say(message:)
函數(shù)依然返回了值。沒(méi)有定義返回類型的函數(shù)會(huì)返回一個(gè)特殊的Void
值锭碳。它其實(shí)是一個(gè)空的元組tuple
袁稽,沒(méi)有任何元素,可以寫成()
擒抛。
多重返回值函數(shù)
你可以用元組tuple
類型讓多個(gè)值作為一個(gè)復(fù)合值從函數(shù)中返回推汽。
func minmax(array: [Int]) -> (min: Int, max: Int) {
let min = array.min()!
let max = array.max()!
return (min, max)
}
let (min, max) = minmax(array: [2, 4, 9, 1])
print("min: \(min), max: \(max)")
// 打印 min: 1, max: 9
可選元組返回類型
如果函數(shù)返回的元組類型有可能整個(gè)元組都沒(méi)有值补疑,你可以使用可選的optional
元組返回類型反映整個(gè)元組可以是nil
的事實(shí)。
可選元組類型如(Int, Int)?
與元組包含可選類型如(Int?, Int?)
是不同的歹撒×椋可選的元組類型,整個(gè)元組是可選的栈妆,而不只是元組中的每個(gè)元素值。
func minmax(array: [Int]) -> (min: Int, max: Int)? {
if array.count == 0 {
return nil
}
let min = array.min()!
let max = array.max()!
return (min, max)
}
// 通過(guò)可選綁定取值
if let (min, max) = minmax(array: [2, 4, 9, 1]) {
print("min: \(min), max: \(max)")
}
函數(shù)參數(shù)標(biāo)簽和參數(shù)名稱
每個(gè)函數(shù)參數(shù)都有一個(gè)參數(shù)標(biāo)簽argument label
以及一個(gè)參數(shù)名稱parameter name
厢钧。參數(shù)標(biāo)簽在調(diào)用函數(shù)的時(shí)候使用鳞尔;調(diào)用的時(shí)候需要將函數(shù)的參數(shù)標(biāo)簽寫在對(duì)應(yīng)的參數(shù)前面。參數(shù)名稱在函數(shù)的實(shí)現(xiàn)中使用早直。默認(rèn)情況下寥假,函數(shù)參數(shù)使用參數(shù)名稱來(lái)作為它們的參數(shù)標(biāo)簽。
func someFunction(firstParameterName: Int, secondParameterName: Int) {
// firstParameterName 和 secondParameterName 代表參數(shù)中的第一個(gè)和第二個(gè)參數(shù)值
}
someFunction(firstParameterName: 1, secondParameterName: 2)
所有的參數(shù)都必須有一個(gè)獨(dú)一無(wú)二的名字霞扬。雖然多個(gè)參數(shù)擁有同樣的參數(shù)標(biāo)簽是可能的糕韧,但是一個(gè)唯一的函數(shù)標(biāo)簽?zāi)軌蚴鼓愕拇a更具可讀性。
指定參數(shù)標(biāo)簽
你可以在參數(shù)名稱前指定它的參數(shù)標(biāo)簽喻圃,中間以空格分隔:
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印 "Hello Bill! Glad you could visit from Cupertino."
忽略參數(shù)標(biāo)簽
如果你不希望為某個(gè)參數(shù)添加一個(gè)標(biāo)簽萤彩,可以使用一個(gè)下劃線_
來(lái)代替一個(gè)明確的參數(shù)標(biāo)簽。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// firstParameterName 和 secondParameterName 代表參數(shù)中的第一個(gè)和第二個(gè)參數(shù)值
}
someFunction(1, secondParameterName: 2)
默認(rèn)參數(shù)值
你可以在函數(shù)體中通過(guò)給參數(shù)賦值來(lái)為任意一個(gè)參數(shù)定義默認(rèn)值Deafult Value
斧拍。當(dāng)默認(rèn)值被定義后雀扶,調(diào)用這個(gè)函數(shù)時(shí)可以忽略這個(gè)參數(shù)。
func double(num: Int, multiple: Int = 2) -> Int {
return num * multiple
}
print(double(num: 5))
// 打印 10
可變參數(shù)
一個(gè)可變參數(shù)variadic parameter
可以接受零個(gè)或多個(gè)值肆汹。函數(shù)調(diào)用時(shí)愚墓,你可以用可變參數(shù)來(lái)指定函數(shù)參數(shù)可以被傳入不確定數(shù)量的輸入值。通過(guò)在變量類型名后面加入...
的方式來(lái)定義可變參數(shù)昂勉。
func sum(_ numbers: Int...) -> Int {
var sum = 0
for num in numbers {
sum = sum + num
}
return sum
}
print(sum(2, 3, 5))
注意:一個(gè)函數(shù)最多只能擁有一個(gè)可變參數(shù)浪册。
輸入輸出參數(shù)
函數(shù)參數(shù)默認(rèn)是常量。試圖在函數(shù)體中更改參數(shù)值將會(huì)導(dǎo)致編譯錯(cuò)誤岗照。如果你想要一個(gè)函數(shù)可以修改參數(shù)的值村象,并且想要在這些修改在函數(shù)調(diào)用結(jié)束后仍然存在,那么就應(yīng)該把這個(gè)參數(shù)定義為輸入輸出參數(shù)In-Out Parameters
攒至。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temp = a;
a = b
b = temp
}
var a = 3
var b = 5
swapTwoInts(&a, &b)
print("a: \(a), b: \(b)")
// 打印 a: 5, b: 3
函數(shù)類型
每個(gè)函數(shù)都有種特定的函數(shù)類型煞肾,函數(shù)的類型由函數(shù)的參數(shù)類型和返回類型組成。
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
函數(shù)的類型是(Int, Int) -> Int
嗓袱。
func printHelloWorld() {
print("hello, world")
}
函數(shù)的類型是() -> Void
籍救。
使用函數(shù)類型
var mathFunction: (Int, Int) -> Int = addTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"
addTwoInts
和mathFunction
有同樣的類型,并讓這個(gè)新變量指向addTwoInts
函數(shù)”渠抹,就可以用mathFunction
來(lái)調(diào)用被賦值的函數(shù)了蝙昙。
函數(shù)類型作為參數(shù)類型
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// 打印 "Result: 8"
函數(shù)類型作為返回類型
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
嵌套函數(shù)
到目前為止本章中你所見(jiàn)到的所有函數(shù)都叫全局函數(shù)global functions
闪萄,它們定義在全局域中。你也可以把函數(shù)定義在別的函數(shù)體中奇颠,稱作嵌套函數(shù)nested functions
败去。
func getTwice(_ num: Int) -> () -> Int {
let multiple = 2
func twice () -> Int {
return num * multiple
}
return twice
}
閉包
Swift
的閉包表達(dá)式擁有簡(jiǎn)潔的風(fēng)格,并鼓勵(lì)在常見(jiàn)場(chǎng)景中進(jìn)行語(yǔ)法優(yōu)化烈拒,主要優(yōu)化如下:
- 利用上下文推斷參數(shù)和返回值類型
- 隱式返回單表達(dá)式閉包圆裕,即單表達(dá)式閉包可以省略
return
關(guān)鍵字 - 參數(shù)名稱縮寫
- 尾隨閉包語(yǔ)法
閉包表達(dá)式
閉包表達(dá)式語(yǔ)法有如下的一般形式:
{ (parameters) -> returnType in
statements
}
以sorted(by:)
方法為例,
let numbers = [3, 9, 5, 1, 7]
sorted(by:)
方法接受一個(gè)閉包荆几,排序閉包函數(shù)類型需為(Int, Int) -> Bool
吓妆。
func backward(_ num1: Int, _ num2: Int) -> Bool {
return num1 > num2
}
然而,以這種方式來(lái)編寫一個(gè)實(shí)際上很簡(jiǎn)單的表達(dá)式num1 > num2
吨铸,確實(shí)太過(guò)繁瑣了行拢。對(duì)于這個(gè)例子來(lái)說(shuō),利用閉包表達(dá)式語(yǔ)法可以更好地構(gòu)造一個(gè)內(nèi)聯(lián)排序閉包诞吱。
let backwardNums = numbers.sorted(by: {(num1, num2) -> Bool in
num1 > num2
})
根據(jù)上下文推斷類型
Swift
可以推斷其參數(shù)和返回值的類型舟奠,sorted(by:)
方法被一個(gè)Int
數(shù)組調(diào)用,因此其參數(shù)必須是(Int, Int) -> Bool
類型的函數(shù)房维。這意味著參數(shù)和返回值類型并不需要作為閉包表達(dá)式定義的一部分沼瘫。因?yàn)樗械念愋投伎梢员徽_推斷:
let backwardNums = numbers.sorted(by: {num1, num2 in return num1 > num2})
單表達(dá)式閉包隱式返回
單行表達(dá)式閉包可以通過(guò)省略return
關(guān)鍵字來(lái)隱式返回單行表達(dá)式的結(jié)果,如上版本的例子可以改寫為:
let backwardNums = numbers.sorted(by: {num1, num2 in num1 > num2})
參數(shù)名稱縮寫
Swift
自動(dòng)為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能咙俩,你可以直接通過(guò) $0
晕鹊,$1
,$2
來(lái)順序調(diào)用閉包的參數(shù)暴浦,以此類推溅话。
let backwardNums = numbers.sorted(by: {$0 > $1})
運(yùn)算符方法
實(shí)際上還有一種更簡(jiǎn)短的方式來(lái)編寫上面例子中的閉包表達(dá)式。Swift
的Int
類型定義了關(guān)于大于號(hào)>
的實(shí)現(xiàn)歌焦,其作為一個(gè)函數(shù)接受兩個(gè)Int
類型的參數(shù)并返回Bool
類型的值飞几。而這正好與sorted(by:)
方法的參數(shù)需要的函數(shù)類型相符合。因此独撇,你可以簡(jiǎn)單地傳遞一個(gè)大于號(hào)屑墨,Swift
可以自動(dòng)推斷出你想使用大于號(hào)的字符串函數(shù)實(shí)現(xiàn):
let backwardNums = numbers.sorted(by: >)
尾隨閉包
如果你需要將一個(gè)很長(zhǎng)的閉包表達(dá)式作為最后一個(gè)參數(shù)傳遞給函數(shù),可以使用尾隨閉包來(lái)增強(qiáng)函數(shù)的可讀性纷铣。尾隨閉包是一個(gè)書寫在函數(shù)括號(hào)之后的閉包表達(dá)式卵史,函數(shù)支持將其作為最后一個(gè)參數(shù)調(diào)用。在使用尾隨閉包時(shí)搜立,你不用寫出它的參數(shù)標(biāo)簽:
func myFunc(completion: () -> Void) {
completion()
}
不使用尾隨閉包進(jìn)行函數(shù)調(diào)用:
myFunc(completion: {
print("hello world")
})
使用尾隨閉包進(jìn)行調(diào)用:
myFunc() {
print("hello world")
}
如果閉包表達(dá)式是函數(shù)或方法的唯一參數(shù)以躯,則當(dāng)你使用尾隨閉包時(shí),你甚至可以把()
省略掉:
myFunc {
print("hello world")
}
值捕獲
閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經(jīng)不存在忧设,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值刁标。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
如果我們單獨(dú)考慮嵌套函數(shù)incrementer()
,會(huì)發(fā)現(xiàn)它有些不同尋常:
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
incrementer()
函數(shù)并沒(méi)有任何參數(shù)址晕,但是在函數(shù)體內(nèi)訪問(wèn)了 runningTotal
和amount
變量膀懈。這是因?yàn)樗鼜耐鈬瘮?shù)捕獲了runningTotal
和amount
變量的引用。捕獲引用保證了runningTotal
和amount
變量在調(diào)用完makeIncrementer
后不會(huì)消失谨垃,并且保證了在下一次執(zhí)行incrementer
函數(shù)時(shí)启搂,runningTotal
依舊存在。
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 返回的值為10
incrementByTen()
// 返回的值為20
incrementByTen()
// 返回的值為30
Swift
閉包可以在其被定義的上下文中捕獲常量或變量刘陶,而Objective-C
需要使用__block
捕獲上下文中常量或變量胳赌;所以閉包不同于block
。
閉包是引用類型
上面的例子中易核,`incrementByTen是常量匈织,但是這些常量指向的閉包仍然可以增加其捕獲的變量的值浪默。這是因?yàn)楹瘮?shù)和閉包都是引用類型牡直。
無(wú)論你將函數(shù)或閉包賦值給一個(gè)常量還是變量,你實(shí)際上都是將常量或變量的值設(shè)置為對(duì)應(yīng)函數(shù)或閉包的引用纳决。上面的例子中碰逸,指向閉包的引用incrementByTen
是一個(gè)常量,而并非閉包內(nèi)容本身阔加。
解決閉包引起的循環(huán)強(qiáng)引用
Swift
有如下要求:只要在閉包內(nèi)使用self
的成員饵史,就要用self.someProperty
或者self.someMethod()
(而不只是someProperty
或someMethod()
)。這提醒你可能會(huì)一不小心就捕獲了self
胜榔,造成循環(huán)強(qiáng)引用胳喷。
定義捕獲列表
捕獲列表中的每一項(xiàng)都由一對(duì)元素組成,一個(gè)元素是weak
或unowned
關(guān)鍵字夭织,另一個(gè)元素是類實(shí)例的引用(例如self
)或初始化過(guò)的變量(如delegate = self.delegate!
)吭露。這些項(xiàng)在方括號(hào)中用逗號(hào)分開(kāi)。
如果閉包有參數(shù)列表和返回類型尊惰,把捕獲列表放在它們前面:
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 這里是閉包的函數(shù)體
}
如果閉包沒(méi)有指明參數(shù)列表或者返回類型讲竿,即它們會(huì)通過(guò)上下文推斷,那么可以把捕獲列表和關(guān)鍵字in放在閉包最開(kāi)始的地方:
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// 這里是閉包的函數(shù)體
}
注意:
在閉包和捕獲的實(shí)例總是互相引用并且總是同時(shí)銷毀時(shí)弄屡,將閉包內(nèi)的捕獲定義為無(wú)主引用题禀。
如果被捕獲的引用絕對(duì)不會(huì)變?yōu)閚il,應(yīng)該用無(wú)主引用膀捷,而不是弱引用迈嘹。
逃逸閉包
當(dāng)一個(gè)閉包作為參數(shù)傳到一個(gè)函數(shù)中,但是這個(gè)閉包在函數(shù)返回之后才被執(zhí)行全庸,我們稱該閉包從函數(shù)中逃逸江锨。當(dāng)你定義接受閉包作為參數(shù)的函數(shù)時(shí)吃警,你可以在參數(shù)名之前標(biāo)注@escaping
,用來(lái)指明這個(gè)閉包是允許逃逸出這個(gè)函數(shù)的啄育。
一種能使閉包逃逸出函數(shù)的方法是酌心,將這個(gè)閉包保存在一個(gè)函數(shù)外部定義的變量中桩砰。舉個(gè)例子唐全,很多啟動(dòng)異步操作的函數(shù)接受一個(gè)閉包參數(shù)作為completion handler
。這類函數(shù)會(huì)在異步操作開(kāi)始之后立刻返回昧绣,但是閉包直到異步操作結(jié)束后才會(huì)被調(diào)用氓英。在這種情況下侯勉,閉包需要“逃逸”出函數(shù),因?yàn)殚]包需要在函數(shù)返回之后被調(diào)用铝阐。例如:
var completionHandlers: [() -> Void] = []
func escapingClosureFunc(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
將一個(gè)閉包標(biāo)記為@escaping
意味著你必須在閉包中顯式地引用self
址貌。
自動(dòng)閉包
自動(dòng)閉包是一種自動(dòng)創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式徘键。這種閉包不接受任何參數(shù)练对,當(dāng)它被調(diào)用的時(shí)候,會(huì)返回被包裝在其中的表達(dá)式的值吹害。這種便利語(yǔ)法讓你能夠省略閉包的花括號(hào)螟凭,用一個(gè)普通的表達(dá)式來(lái)代替顯式的閉包。
自動(dòng)閉包讓你能夠延遲求值它呀,因?yàn)橹钡侥阏{(diào)用這個(gè)閉包螺男,代碼段才會(huì)被執(zhí)行。延遲求值對(duì)于那些有副作用和高計(jì)算成本的代碼來(lái)說(shuō)是很有益處的纵穿,因?yàn)樗沟媚隳芸刂拼a的執(zhí)行時(shí)機(jī)下隧。下面的代碼展示了閉包如何延時(shí)求值。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出 "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"