函數(shù) (Functions)
- 函數(shù)的定義方法和ObjC差別很大, 第一次看起來會(huì)比較奇怪, 話說ObjC中[]這種方法調(diào)用方式都看過來了, 還有別的能難倒我們嗎? 直接以例子來看吧:
func sayHello(personName: String) -> String {
let greeting = "Hello, " + personName + "!"
return greeting
}
// 接受一個(gè)String的參數(shù), 返回一個(gè)String
//如果不需要參數(shù)不返回?cái)?shù)據(jù)則是:
func sayHello() -> (){ // 當(dāng)然也可以省略后面的 ->()
print("Hello!")
}
- 函數(shù)返回多個(gè)值
當(dāng)然是利用強(qiáng)大的元組了,
func minMax(array: [Int]) -> (min: Int, max: Int) {
// find min value and max value in array
return (min, max)
}
// 調(diào)用
let bounds = minMax([8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
- 返回Optionals value
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
// find min value and max value in array
return (min, max)
}
// 調(diào)用
if let bounds = minMax([8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
- 函數(shù)參數(shù)名:
前面函數(shù)可以看到, 如果只有一個(gè)參數(shù), 名字是會(huì)被省略的, 具體原因我們可以從蘋果自己的命名看出來, Array里面有個(gè)joinWithSeparator(separator: String)方法, 可以看出, 蘋果希望第一個(gè)參數(shù)名是包含在函數(shù)名里面的, 如果不這樣做的話, 調(diào)用的時(shí)候大概是這樣的:
joinWith(separator:"")
比較下:
joinWithSeparator("")
我個(gè)人是傾向于官方的版本, 因?yàn)檫@樣一來函數(shù)重名的概率就更小了, 在找方法的時(shí)候也更迅速, 同時(shí)用模糊匹配的插件也會(huì)更快. 但是可能又有人覺得這樣感覺不一致, 看大家的喜好吧
上面都是單參數(shù)的例子, 舉個(gè)多參數(shù)的例子:
func someFunction(firstParameterName: Int, secondParameterName: Int) {
}
someFunction(1, secondParameterName: 2)
// 第一個(gè)參數(shù)自動(dòng)隱藏, 如果一定要顯示出來就多寫一個(gè)標(biāo)簽
func otherFunction(firstParameterName firstParameterName: Int, secondParameterName: Int) {
}
otherFunction(firstParameterName: 1, secondParameterName: 2)
// 同樣的 要是不想標(biāo)簽和變量名一樣 也可以自己指定
func sayHello(to person: String, and anotherPerson: String) -> String {
return "Hello \(person) and \(anotherPerson)!"
}
print(sayHello(to: "Bill", and: "Ted"))
// 如果你想像C語言那么簡潔, 不想要參數(shù)標(biāo)簽, 那么可以用下劃線來隱藏掉
func addTwoNumber(a: Int, _ b: Int) {
}
addTwoNumber(1, 2)
5 默認(rèn)參數(shù)值:
不是什么新鮮玩意, 但是卻是有用的東西, 在C++里面早已經(jīng)有了, 但是ObjC里面因?yàn)闆]有所以在暴露一些接口的時(shí)候非常蛋疼.
func someFunction(parameterWithDefault: Int = 12) {
print(parameterWithDefault)
}
someFunction(6) // 6
someFunction() // 12
6 不限參數(shù)
這個(gè)東西看起來比較高大上, 實(shí)際上原理應(yīng)該是比較簡單的, 先看例子吧:
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5) // 返回3.0
arithmeticMean(3, 8.25, 18.75) //返回10.0
// 因?yàn)轭愋褪且粯拥? 所以原理就是編譯器直接把參數(shù)替換成為[Double]類型, 然后在調(diào)用處自己組裝一個(gè)數(shù)組出來即可
7 常量參數(shù)和變量參數(shù)
函數(shù)入?yún)⒛J(rèn)是常量, 不允許修改的, 但是有時(shí)候不想重新定義一個(gè)變量出來, 就想要直接用這個(gè)變量就好了, 那么就加上一個(gè)在參數(shù)前面加上一個(gè)var即可, 例如:
func modifyParamFunction(var a: Int, b: Int) ->Int{
a = b
b = a // error
return a
}
8 修改參數(shù)本身
在C和ObjC語言中, 修改參數(shù)本身的值是需要傳遞地址的, 在Swift里面也一樣, 不過因?yàn)闆]有指針運(yùn)算符, 所以需要顯式地在參數(shù)那里加一個(gè)inout, 例如:
func swapTwoInts(inout a: Int, inout _ b: Int) {
let t = a
a = b
b = t
}
var a = 1, b = 2
swapTwoInts(&a, &b) // &去掉會(huì)報(bào)錯(cuò)
9 函數(shù)類型
在Swift中, 函數(shù)本身也是一個(gè)變量, 自然就會(huì)有類型,例如,
上面sayHello這個(gè)函數(shù), 類型就是 ()->(), 我們可以把它賦值給一個(gè)變量:
var sayHi: (String)->(String) = sayHello
sayHi("Ryan") // 調(diào)用之
10 函數(shù)作為參數(shù)傳入
ObjC里面也有差不多的用法, 既然函數(shù)是一個(gè)對象, 且有類型, 那么直接把這個(gè)對象傳入即可, 例如:
func addTwoInts(a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(a: Int, _ b: Int) -> Int {
return a * b
}
func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
11 函數(shù)作為返回值
與上面一樣的道理, 直接看例子:
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}
var currentValue = 3
let moveNearerToZero = chooseStepFunction(currentValue > 0)
while currentValue != 0 {
currentValue = moveNearerToZero(currentValue)
}
12 嵌套函數(shù)
也就是函數(shù)里面還可以聲明并實(shí)現(xiàn)函數(shù), 只是這樣一來就無法在外部訪問了, 可以在一些函數(shù)里面寫一些簡單的輔助功能, 同時(shí)這些功能又沒有需要暴露的情況, 這樣可以很好地保持其封裝性, 如上面的例子可以寫成:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
函數(shù)大概就這么多, 只要語法看習(xí)慣了, 其實(shí)也沒什么太多的區(qū)別, 在類中實(shí)例方法也是一樣的定義, 類方法在func前面加個(gè)class罷了...
同樣, 需要看更多細(xì)節(jié)的, 查看官方文檔
閉包(Closure)
到了Swift讓我驚艷的部分了, 閉包和block的作用基本是相似的, 只是比起block來, 蘋果把閉包做的更加簡潔, 閉包的內(nèi)容很多, 但是如果有block的基礎(chǔ)的話, 應(yīng)該不會(huì)難懂.
- 閉包表達(dá)式(Closure Expressions)
我們之前提到過一次閉包, 在數(shù)組排序的時(shí)候, 在數(shù)組的sort()函數(shù)中, 我們可以傳入函數(shù), 也可以傳入閉包(所以從某種角度來說, 這兩個(gè)東西就是一樣的)
func backwards(s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
// reversed 等于 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
如果用閉包來寫就是這樣的:
reversed = names.sort({ (s1: String, s2: String) -> Bool in
return s1 > s2
})
// 兇殘的蘋果對閉包的表達(dá)式進(jìn)行了一輪又一輪的簡化, 直達(dá)之前提過的最簡表達(dá)式
// 第一輪:
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } ) // 寫成一行業(yè)不算什么
// 第二輪:
reversed = names.sort( { s1, s2 in return s1 > s2 } )// 根據(jù)類型推導(dǎo)可以省略類型
// 第三輪:
reversed = names.sort( { s1, s2 in s1 > s2 } ) // 排序需要一個(gè)Bool值, 那么s1>s2這個(gè)表達(dá)式符合需求 省略return
// 第四輪:
reversed = names.sort( { $0 > $1 } ) // 知道會(huì)有2個(gè)參數(shù), 入?yún)⒌膶懛饬?// 第五輪:
reversed = names.sort(>) // 完全交給編譯器來推導(dǎo)...
總之, 上面這些寫法對于這種簡單一點(diǎn)的閉包來說, 還是很好用的, 個(gè)人建議到一般到第三輪, 很簡單到第四輪就差不多了
- 尾隨閉包(Trailing Closures)
如果一個(gè)函數(shù)需要一個(gè)閉包作為參數(shù), 那么可以把閉包寫在函數(shù)調(diào)用的括號(hào)里, 也可以寫在外面, 例如:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
someFunctionThatTakesAClosure({})
someFunctionThatTakesAClosure(){}
// 又比如:
reversed = names.sort() { $0 > $1 }
// 還可以連()都省略
reversed = names.sort { $0 > $1 }
當(dāng)然, 這么寫肯定是要求閉包是最后一個(gè)參數(shù)的
- 值捕獲(Capturing Values)
簡單說來就是閉包可以使用外部的一些變量或者常量, 即使外部已經(jīng)不存在了, 這里依然會(huì)保留, 直接看例子吧:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 返回 10
incrementByTen()
// 返回 20
incrementByTen()
// 返回 30
// 由此可見, makeIncrementer函數(shù)內(nèi)部的runningTotal沒有被銷毀, 如果沒猜錯(cuò)的話, incrementByTen銷毀了才會(huì)銷毀掉(如果沒有別的引用了)
// 如果又創(chuàng)建了一個(gè)incrementor, 則會(huì)是新的一份runningTotal
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回 7
很顯然, 這里需要注意閉包中的循環(huán)引用問題
閉包是引用類型
差不多記住這句話就好了, 所以要更加注意循環(huán)引用的問題非逃逸閉包(Nonescaping Closures)
官網(wǎng)解釋了很多, 但是聽起來還是云里霧里的, 最后總算搞明白了...簡單說來就是這個(gè)閉包被作為參數(shù)傳進(jìn)去之后, 如果函數(shù)返回了, 我們還能不能再用了, 默認(rèn)是可以再用, 如果你不想讓它被重復(fù)利用, 就加上@noescape, 通過例子來說吧:
var completionHandlers: [() -> Void] = [] // 存儲(chǔ)閉包用
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
closure()
completionHandlers.append(closure) // error
}
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure { () -> Void in
print("OK")
}
completionHandlers.first?() // 把問號(hào)換成!也可以, 因?yàn)榇_定非空
加了@noescape的閉包, 必須當(dāng)場使用, 不允許出了函數(shù)再用, 但是沒有加的就可以.
- 自動(dòng)閉包(Autoclosures)
還是回到Array的sort那里, 為什么可以省略掉{}呢? 因?yàn)橛凶詣?dòng)閉包, 所謂自動(dòng)閉包就是把傳入的參數(shù)自動(dòng)包裝成一個(gè)閉包, 直接看官方例子:
func serveCustomer(@autoclosure customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))
// 如果函數(shù)沒有@autoclosure則調(diào)用就是:
//serveCustomer({customersInLine.removeAtIndex(0)})
閉包差不多介紹到這里, 很多新的概念, 其實(shí)蘋果的宗旨就是, 能怎么簡潔就這么簡潔, 等習(xí)慣之后看起來還是很舒服, 很自然的.
按慣例, 具體細(xì)節(jié)看看官方文檔
后記
目前我也只看到這里, 所以一次更新到了閉包, 之后更新速度可能會(huì)慢, 下周還要出差...囧rz..感謝大家花時(shí)間看, 希望能有幫助, 也希望大家給出意見, 不對之處請指正.