最近真正開始學 Swift,在調用函數的時候遇到一個問題:到底寫不寫函數名?
我們來看兩個個例子:
// 1 func test(a: Int, b: Int) ->Int { return a + b } test(a: 1, b: 1) // (A) test(1, b:1) // (B) //2 class Test { var name: String var age: Int init(name: String, age: Int) { self.name = name self.age = age } func sayHello(word: String, place: String) { println("Hello \(self.name), \(word) at \(place)") } } var test = Test("Jack", age: 12) // (C) test.sayHello(word: "nice to meet you", place: "Beijing") // (D)
(A)
、(B)
、(C)
、(D)
四處調用,哪個會報錯?
請
仔
細
思
考
一
下
,
或
者
打
開
Playground
運
行
一
下
。
好吧,如果你還是直接翻到這里,那我也無能為力了。
答案是:四處全部報錯。
正確的寫法是:
test(1, 1) var test = Test(name: "Jack", age: 12) test.sayHello("nice to meet you", place: "Beijing")
腳麻了嗎?麻了就對了,我跺我也麻。
到底咋回事
首先我們要清楚,Swift 中的調用有三種:
- 函數調用(閉包也歸於函數,雖然所有函數本質上都是閉包。這句話看不懂的自動跳過,只是為了防人摳字眼)
- 類初始化
- 方法調用
如果沒有參數,那自然直接()
調用,因此下面的討論前提是需要傳參,並且傳參數量大於一。
上一節的例子就是典型的三種調用,傳參的時候正確寫法如下:
<函數名>(參數值,參數值...) // 不加任何參數名,直接寫參數值 <實例>.<方法名>(參數值,參數名:參數值,參數名:參數值...) // 方法調用第一個參數不寫參數名,后面的全部要寫。特殊情況是尾閉包,往下看 <類初始化>(參數名:參數值,參數名:參數值...) // 類初始化所有參數都需要加參數名
單個函數的調用很好理解,其他語言里也大多是這么做的。我們主要解釋方法調用和類初始化這兩種調用。
為什么 Swift 對方法調用和類初始化的參數名有如此奇怪的限制?主要原因是繼承 Objective-C 的一貫傳統。我們來看看 OC 里面的寫法:
[person setName:@"sam" andSecondName:@"job"]
setName
是方法名,后面緊跟第一個參數,對應 Swift 中的寫法是:
person.setName("sam", andSecondName: "job")
也就是說,方法名中已經隱含了第一個參數的名字(雖然我們不知道第一個參數名是什么,但是顯然第一個參數是Name
,我們就可以知道第一個參數是名字),所以省略第一個參數名。
那么init
為什么要加上第一個參數名?
直接看代碼:
[Test initWith:"Sam", andSecondName: "job"] // oc Test(name: "Sam", andSecondName: "job") / swift
由於 Swift 中初始化時候直接使用類名,沒有方法名,所以第一個參數名就不能省略了。
特殊情況
下面介紹幾種特殊情況。
尾閉包
首先是尾閉包。
Swift 中許多方法的最后一個參數是handler
,我們可以傳入一個閉包。由於閉包寫到參數列表里比較繁瑣,Swift 提供了一種新寫法:尾閉包。看例子:
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { ...... })
UIAlertAction
的最后一個參數是handler
,這里用尾閉包來寫的話,就是在右括號后面直接加閉包。當然,你也可以把閉包寫到參數列表里,只是需要加參數名。
如果函數只需要handler
一個參數,可以省略方法調用的圓括號:
aaa.sort { ... }
默認值
參數可以寫默認值,但是默認值有許多規矩:
- 如果使用默認值,調用的時候,默認值對應的參數必須寫參數名。這里影響的主要是函數和方法調用,因為類初始化本來就要寫全參數名。
- 如果使用默認值並且默認值不是出現在最后,那調用的時候必須寫全所有參數。
綜合以上兩點,建議大家在使用默認值的時候,把帶默認值的參數放在列表結尾,這樣會方便許多。
強制指定參數名
如果你想強制要求調用時必須加參數名,可以在聲明的時候給參數加上外部參數名:
func test(outName name: String, outAge age: Int) { ... } test(outName: "asd", outAge: 2)
這樣調用的時候必須加上對應的外部參數名。
如果外部參數名和內部參數名一樣,可以直接在參數名前加#
:
func test(#name: String, #age: Int) { ... } test(outName: "asd", outAge: 2)
強制取消參數名
對於需要參數名的函數,你也可以在參數名前加_
來強制取消參數名:
class Test { func test(name: String, _ age: Int) { ... } } var test = Test() test.test("123", 3)
總之
Swift 中的函數調用真是個坑。