雖然 Objective-C 的語法相對于其他編程語言來說寫法有點奇怪,但是當你真正使用的時候它的語法還是相當?shù)暮唵?。下面有一些例子?/p>
+ (void)mySimpleMethod
{
// 類方法
// 無參數(shù)
// 無返回值
}
- (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2
{
// 實例方法
// 其中一個參數(shù)是 NSString 指針類型,另一個是 NSNumber 指針類型
// 必須返回一個 NSString 指針類型的值
return @"hello, world!";
}
相比而言,雖然 Swift 的語法看起來與其他編程語言有更多相似的地方,但是它也可以比 Objective-C 更加復雜和令人費解。
在繼續(xù)之前,我需要澄清 Swift 中方法和函數(shù)之間的不同,因為在本文中我們將使用這兩個術(shù)語。按照 Apple 的 Swift Programming Language Book 里面的方法定義:
方法是與某些特定類型相關(guān)聯(lián)的函數(shù)。類、結(jié)構(gòu)體、枚舉都可以定義實例方法;實例方法為給定類型的實例封裝了具體的任務與功能。類、結(jié)構(gòu)體、枚舉也可以定義類型方法,類型方法與類型本身相關(guān)聯(lián)。類型方法與 Objective-C 中的類方法(class methods)相似。
以上是長文慎讀。一句話:函數(shù)是獨立的,而方法是函數(shù)封裝在類,結(jié)構(gòu)或者枚舉中的函數(shù)。
讓我們從簡單的 “Hello,World” Swift 函數(shù)開始:
func mySimpleFunction() {
println("hello, world!")
}
如果你曾在 Objective-C 之外的語言進行過編程,上面的這個函數(shù)你會非常熟悉
func 表示這是一個函數(shù)。mySimpleFunction。( )。{ }中執(zhí)行現(xiàn)在讓我們看一個稍稍復雜的例子:
func myFunctionName(param1: String, param2: Int) -> String {
return "hello, world!"
}
這個函數(shù)有一個 String 類型且名為 param1 的參數(shù)和一個 Int 類型名為 param2 的參數(shù)并且返回值是 `String 類型。
Swift 和 Objective-C 之間其中一個巨大的差別就是當 Swift 函數(shù)被調(diào)用的時候參數(shù)工作方式。如果你像我一樣喜歡 Objective-C 超長的命名方式,那么請記住,在默認情況下 Swift 函數(shù)被調(diào)用時參數(shù)名是不被外部調(diào)用包含在內(nèi)的。
func hello(name: String) {
println("hello \(name)")
}
hello("Mr. Roboto")
在你增加更多參數(shù)到函數(shù)之前,一切看起來不是那么糟糕。但是:
func hello(name: String, age: Int, location: String) {
println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
}
hello("Mr. Roboto", 5, "San Francisco")
如果僅閱讀 hello("Mr. Roboto", 5, "San Francisco"),你可能很難知道每一個參數(shù)代表什么。
在 Swift 中,有一個概念稱為 *外部參數(shù)名稱 * 用來解決這個困惑:
func hello(fromName name: String) {
println("\(name) says hello to you!")
}
hello(fromName: "Mr. Roboto")
上面函數(shù)中,fromName 是一個外部參數(shù),在函數(shù)被調(diào)用的時候?qū)⒈话ㄔ谡{(diào)用中。在函數(shù)內(nèi)執(zhí)行時,使用 name 這個內(nèi)部參數(shù)來對輸入進行引用。
如果你希望外部參數(shù)和內(nèi)部參數(shù)相同,你不需要寫兩次參數(shù)名:
func hello(name name: String) {
println("hello \(name)")
}
hello(name: "Robot")
只需要在參數(shù)前面添加 # 的快捷方式:
func hello(#name: String) {
println("hello \(name)")
}
hello(name: "Robot")
當然,對于方法而言參數(shù)的工作方式略有不同...
當被封裝在類 (或者結(jié)構(gòu),枚舉) 中時,方法的第一個參數(shù)名不被外部包含,同時所有的后面的參數(shù)在方法調(diào)用時候被外部包含:
class MyFunClass {
func hello(name: String, age: Int, location: String) {
println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
}
}
let myFunClass = MyFunClass()
myFunClass.hello("Mr. Roboto", age: 5, location: "San Francisco")
因此最佳實踐是在方法名里包含第一個參數(shù)名,就像 Objective-C 那樣:
class MyFunClass {
func helloWithName(name: String, age: Int, location: String) {
println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
}
}
let myFunClass = MyFunClass()
myFunClass.helloWithName("Mr. Roboto", age: 5, location: "San Francisco")
相對于調(diào)用函數(shù) “hello”,我將其重命名為 helloWithName,這使得第一個參數(shù) name 變得很清晰。
如果出于一些原因你希望在函數(shù)中跳過外部參數(shù)名 (我建議如果要這么做的話,你需要一個非常好的理由),為外部函數(shù)添加 _ 來解決:
class MyFunClass {
func helloWithName(name: String, _ age: Int, _ location: String) {
println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
}
}
let myFunClass = MyFunClass()
myFunClass.helloWithName("Mr. Roboto", 5, "San Francisco")
需要注意一個非常酷的是 Swift 中實例方法是柯里化函數(shù)。
柯里化背后的基本想法是函數(shù)可以局部應用,意思是一些參數(shù)值可以在函數(shù)調(diào)用之前被指定或者綁定。這個部分函數(shù)的調(diào)用會返回一個新的函數(shù)。
如果我有一個類:
class MyHelloWorldClass {
func helloWithName(name: String) -> String {
return "hello, \(name)"
}
}
我可以建立一個變量指向類中的 helloWithName 函數(shù):
let helloWithNameFunc = MyHelloWorldClass.helloWithName
// MyHelloWorldClass -> (String) -> String
我新的 helloWithNameFunc 是 MyHelloWorldClass -> (String) -> String 類型,這個函數(shù)接受我的類的實例并返回另一個函數(shù)。新函數(shù)接受一個字符串值,并返回一個字符串值。
所以實際上我可以這樣調(diào)用我的函數(shù):
let myHelloWorldClassInstance = MyHelloWorldClass()
helloWithNameFunc(myHelloWorldClassInstance)("Mr. Roboto")
// hello, Mr. Roboto
在類,結(jié)構(gòu)體或者枚舉初始化的時候?qū)⒄{(diào)用一個特殊的 init 方法。在 Swift 中你可以像其他方法那樣定義初始化參數(shù):
class Person {
init(name: String) {
// your init implementation
// 你的初始化方法實現(xiàn)
}
}
Person(name: "Mr. Roboto")
注意下,不像其他方法,初始化方法的第一個參數(shù)必須在實例時必須是外部的。
大多數(shù)情況下的最佳實踐是添加一個不同的外部參數(shù)名 — 本例中的 fromName —讓初始化更具有可讀性:
class Person {
init(fromName name: String) {
// your init implementation
// 你的初始化方法實現(xiàn)
}
}
Person(fromName: "Mr. Roboto")
當然,就像其他方法那樣,如果你想讓方法跳過外部參數(shù)名的話,可以添加 _。我喜歡 Swift Programming Language Book 初始化例子的強大和可讀性。
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 是 37.0
如果你希望抽象類/枚舉/結(jié)構(gòu)體的初始化,跳過外部參數(shù)可以非常有用。我喜歡在 David Owen 的 json-swift library 中對這項技術(shù)的使用:
public struct JSValue : Equatable {
// ... 截斷的部分代碼
/// 使用 `JSArrayType` 來初始化 `JSValue`。
public init(_ value: JSArrayType) {
self.value = JSBackingValue.JSArray(value)
}
/// 使用 `JSObjectType` 來初始化 `JSValue`。
public init(_ value: JSObjectType) {
self.value = JSBackingValue.JSObject(value)
}
/// 使用 `JSStringType` 來初始化 `JSValue`。
public init(_ value: JSStringType) {
self.value = JSBackingValue.JSString(value)
}
/// 使用 `JSNumberType` 來初始化 `JSValue`。
public init(_ value: JSNumberType) {
self.value = JSBackingValue.JSNumber(value)
}
/// 使用 `JSBoolType` 來初始化 `JSValue`。
public init(_ value: JSBoolType) {
self.value = JSBackingValue.JSBool(value)
}
/// 使用 `Error` 來初始化 `JSValue`。
init(_ error: Error) {
self.value = JSBackingValue.Invalid(error)
}
/// 使用 `JSBackingValue` 來初始化 `JSValue`。
init(_ value: JSBackingValue) {
self.value = value
}
}
相較于 Objective-C,Swift 有很多額外的選項用來指定可以傳入的參數(shù)的類型,下面是一些例子。
在 Swift 中有一個新的概念稱之為 optional types:
可選表示 “那兒有一個值,并且它等于 x ” 或者 “那兒沒有值”??蛇x有點像在 Objective-C 中使用 nil,但是它可以用在任何類型上,不僅僅是類??蛇x類型比 Objective-C 中的 nil 指針更加安全也更具表現(xiàn)力,它是 Swift 許多強大特性的重要組成部分。
表明一個參數(shù)是可選 (可以是 nil),可以在類型規(guī)范后添加一個問號:
func myFuncWithOptionalType(parameter: String?) {
// function execution
}
myFuncWithOptionalType("someString")
myFuncWithOptionalType(nil)
使用可選時候不要忘記拆包!
func myFuncWithOptionalType(optionalParameter: String?) {
if let unwrappedOptional = optionalParameter {
println("The optional has a value! It's \(unwrappedOptional)")
} else {
println("The optional is nil!")
}
}
myFuncWithOptionalType("someString")
// optional has a value! It's someString
myFuncWithOptionalType(nil)
// The optional is nil
如果學習過 Objective-C,那么習慣使用可選值肯定需要一些時間!
func hello(name: String = "you") {
println("hello, \(name)")
}
hello(name: "Mr. Roboto")
// hello, Mr. Roboto
hello()
// hello, you
值得注意的是有默認值的參數(shù)自動包含一個外部參數(shù)名
由于參數(shù)的默認值可以在函數(shù)被調(diào)用時調(diào)過,所以最佳實踐是把含有默認值的參數(shù)放在函數(shù)參數(shù)列表的最后。Swift Programming Language Book 包含相關(guān)的內(nèi)容介紹:
把含有默認值的參數(shù)放在參數(shù)列表最后,可以確保對它的調(diào)用中所有無默認值的參數(shù)順序一致,而且清晰表述了在不同情況下調(diào)用的函數(shù)是相同的。
我是默認參數(shù)的粉絲,主要是它使得代碼容易改變而且向后兼容。比如配置一個自定義的 UITableViewCell 的函數(shù)里,你可以在你的某個用例中用兩個參數(shù)開始,如果另一個用例出現(xiàn),需要另一個參數(shù) (比如你的 Cell 的 label 含有不同文字顏色),只需要添加一個包含新默認值的參數(shù) — 函數(shù)的其他部分已經(jīng)被正確調(diào)用,并且你代碼最新部分僅需要參數(shù)傳入一個非默認值。
可變參數(shù)是傳入數(shù)組元素的一個更加可讀的版本。實際上,比如下面例子中的內(nèi)部參數(shù)名類型,你可以看到它是 [String] 類型 (String 數(shù)組):
func helloWithNames(names: String...) {
for name in names {
println("Hello, \(name)")
}
}
// 2 names
helloWithNames("Mr. Robot", "Mr. Potato")
// Hello, Mr. Robot
// Hello, Mr. Potato
// 4 names
helloWithNames("Batman", "Superman", "Wonder Woman", "Catwoman")
// Hello, Batman
// Hello, Superman
// Hello, Wonder Woman
// Hello, Catwoman
這里要特別記住的是可以傳入 0 個值,就像傳入一個空數(shù)組一樣,所以如果有必要的話,不要忘記檢查空數(shù)組:
func helloWithNames(names: String...) {
if names.count > 0 {
for name in names {
println("Hello, \(name)")
}
} else {
println("Nobody here!")
}
}
helloWithNames()
// Nobody here!
可變參數(shù)另一個要注意的地方是 — 可變參數(shù)必須是在函數(shù)列表的最后一個!
利用 inout 參數(shù),你有能力 (經(jīng)過引用來) 操縱外部變量:
var name1 = "Mr. Potato"
var name2 = "Mr. Roboto"
func nameSwap(inout name1: String, inout name2: String) {
let oldName1 = name1
name1 = name2
name2 = oldName1
}
nameSwap(&name1, &name2)
name1
// Mr. Roboto
name2
// Mr. Potato
這是 Objective-C 中非常常見的用來處理錯誤的模式。 NSJSONSerialization 是其中一個例子:
- (void)parseJSONData:(NSData *)jsonData
{
NSError *error = nil;
id jsonResult = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!jsonResult) {
NSLog(@"ERROR: %@", error.description);
}
}
Swift 非常之新,所以這里沒有一個公認的處理錯誤的方式,但是在 inout 參數(shù)之外肯定有非常多的選擇!看看 David Owen's 最新的博客 Swfit 中的錯誤處理。關(guān)于這個話題的更多內(nèi)容已經(jīng)在 Functional Programming in Swift 中被涵蓋.
我不會在本文中大篇幅介紹泛型,但是這里有個簡單的例子來闡述如何在一個函數(shù)中接受兩個類型不定的參數(shù),但確保這兩個參數(shù)類型是相同的:
func valueSwap<T>(inout value1: T, inout value2: T) {
let oldValue1 = value1
value1 = value2
value2 = oldValue1
}
var name1 = "Mr. Potato"
var name2 = "Mr. Roboto"
valueSwap(&name1, &name2)
name1 // Mr. Roboto
name2 // Mr. Potato
var number1 = 2
var number2 = 5
valueSwap(&number1, &number2)
number1 // 5
number2 // 2
更多的泛型知識,我建議你閱讀下 Swift Programming Language book 中的泛型章節(jié)。
默認情況下,參數(shù)傳入函數(shù)是一個常量,所以它們在函數(shù)范圍內(nèi)不能被操作。如果你想修改這個行為,只需要在你的參數(shù)前使用 var 關(guān)鍵字:
var name = "Mr. Roboto"
func appendNumbersToName(var name: String, #maxNumber: Int) -> String {
for i in 0..<maxNumber {
name += String(i + 1)
}
return name
}
appendNumbersToName(name, maxNumber:5)
// Mr. Robot12345
name
// Mr. Roboto
值得注意的是這個和 inout 參數(shù)不同 — 變量參數(shù)不會修改外部傳入變量!
在 Swift 中,函數(shù)可以被用來當做變量傳遞。比如,一個函數(shù)可以含有一個函數(shù)類型的參數(shù):
func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String {
let luckyNumber = Int(arc4random() % 100)
return lotteryHandler(name, luckyNumber)
}
func defaultLotteryHandler(name: String, luckyNumber: Int) -> String {
return "\(name), your lucky number is \(luckyNumber)"
}
luckyNumberForName("Mr. Roboto", lotteryHandler: defaultLotteryHandler)
// Mr. Roboto, your lucky number is 38
注意下只有函數(shù)的引用被傳入 — 在本例中是 defaultLotteryHandler。這個函數(shù)之后是否執(zhí)行是由接收的函數(shù)決定。
實例方法也可以用類似的方法傳入:
func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String {
let luckyNumber = Int(arc4random() % 100)
return lotteryHandler(name, luckyNumber)
}
class FunLottery {
func defaultLotteryHandler(name: String, luckyNumber: Int) -> String {
return "\(name), your lucky number is \(luckyNumber)"
}
}
let funLottery = FunLottery()
luckyNumberForName("Mr. Roboto", lotteryHandler: funLottery.defaultLotteryHandler)
// Mr. Roboto, your lucky number is 38
為了讓你的函數(shù)定義更具可讀性,可以考慮為你函數(shù)的類型創(chuàng)建別名 (類似于 Objective-C 中的 typedef):
typealias lotteryOutputHandler = (String, Int) -> String
func luckyNumberForName(name: String, #lotteryHandler: lotteryOutputHandler) -> String {
let luckyNumber = Int(arc4random() % 100)
return lotteryHandler(name, luckyNumber)
}
你也可以使用不包含參數(shù)名的函數(shù) (類似于 Objective-C 中的 blocks):
func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String {
let luckyNumber = Int(arc4random() % 100)
return lotteryHandler(name, luckyNumber)
}
luckyNumberForName("Mr. Roboto", lotteryHandler: {name, number in
return "\(name)'s' lucky number is \(number)"
})
// Mr. Roboto's lucky number is 74
在 Objective-C 中,使用 blocks 作為參數(shù)是異步操作是操作結(jié)束時的回調(diào)和錯誤處理的常見方式,這一方式在 Swift 中得到了很好的延續(xù)。
Swift 有三個級別的權(quán)限控制:
默認情況下,每個函數(shù)和變量是 internal 的 —— 如果你希望修改他們,你需要在每個方法和變量的前面使用 private 或者 public 關(guān)鍵字:
public func myPublicFunc() {
}
func myInternalFunc() {
}
private func myPrivateFunc() {
}
private func myOtherPrivateFunc() {
}
Ruby 帶來的習慣,我喜歡把所有的私有函數(shù)放在類的最下面,利用一個 //MARK 來區(qū)分:
class MyFunClass {
func myInternalFunc() {
}
// MARK: Private Helper Methods
private func myPrivateFunc() {
}
private func myOtherPrivateFunc() {
}
}
希望 Swift 在將來的版本中包含一個選項可以用一個私有關(guān)鍵字來表明以下所有的方法都是私有的,類似于其他語言那樣做訪問控制。
在 Swift 中,函數(shù)的返回類型和返回值相較于 Objective-C 而言更加復雜,尤其是引入可選和多個返回類型。
如果你的函數(shù)有可能返回一個 nil 值,你需要指定返回類型為可選:
func myFuncWithOptonalReturnType() -> String? {
let someNumber = arc4random() % 100
if someNumber > 50 {
return "someString"
} else {
return nil
}
}
myFuncWithOptonalReturnType()
當然,當你使用可選返回值,不要忘記拆包:
let optionalString = myFuncWithOptonalReturnType()
if let someString = optionalString {
println("The function returned a value: \(someString)")
} else {
println("The function returned nil")
}
The best explanation I've seen of optionals is from a tweet by @Kronusdark: 對我而言最好的可選值的解釋來自于 @Kronusdark 的一條推特:
我終于弄明白 @SwiftLang 的可選值了,它們就像薛定諤的貓!在你使用之前,你必須先看看貓是死是活。
Swift 其中一個令人興奮的功能是函數(shù)可以有多個返回值
func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int) {
var min = numbers[0]
var max = numbers[0]
for number in numbers {
if number > max {
max = number
}
if number < min {
min = number
}
}
return (min, max)
}
findRangeFromNumbers(1, 234, 555, 345, 423)
// (1, 555)
就像你看到的那樣,在一個元組中返回多個值,這一個非常簡單的將值進行組合的數(shù)據(jù)結(jié)構(gòu)。有兩種方法可以使用多返回值的元組:
let range = findRangeFromNumbers(1, 234, 555, 345, 423)
println("From numbers: 1, 234, 555, 345, 423. The min is \(range.min). The max is \(range.max).")
// From numbers: 1, 234, 555, 345, 423. The min is 1. The max is 555.
let (min, max) = findRangeFromNumbers(236, 8, 38, 937, 328)
println("From numbers: 236, 8, 38, 937, 328. The min is \(min). The max is \(max)")
// From numbers: 236, 8, 38, 937, 328. The min is 8. The max is 937
當返回值是可選的時候,多返回值就比較棘手。對于多個可選值的返回,有兩種辦法解決這種情況。
在上面的例子函數(shù)中,我的邏輯是有缺陷的 —— 它有可能沒有值傳入,所以我的代碼有可能在沒有值傳入的時候崩潰,所以我希望我整個返回值是可選的:
func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int)? {
if numbers.count > 0 {
var min = numbers[0]
var max = numbers[0]
for number in numbers {
if number > max {
max = number
}
if number < min {
min = number
}
}
return (min, max)
} else {
return nil
}
}
if let range = findRangeFromNumbers() {
println("Max: \(range.max). Min: \(range.min)")
} else {
println("No numbers!")
}
// No numbers!
另一種做法是對元組中的每個返回值設為可選來代替整體的元組可選:
func componentsFromUrlString(urlString: String) -> (host: String?, path: String?) {
let url = NSURL(string: urlString)
return (url.host, url.path)
}
如果你決定你元組值中一些值是可選,拆包時候會變得有些復雜,你需要考慮每中單獨的可選返回值的組合:
let urlComponents = componentsFromUrlString("http://name.com/12345;param?foo=1&baa=2#fragment")
switch (urlComponents.host, urlComponents.path) {
case let (.Some(host), .Some(path)):
println("This url consists of host \(host) and path \(path)")
case let (.Some(host), .None):
println("This url only has a host \(host)")
case let (.None, .Some(path)):
println("This url only has path \(path). Make sure to add a host!")
case let (.None, .None):
println("This is not a url!")
}
// This url consists of host name.com and path /12345
如你所見,它和 Objective-C 的處理方式完全不同!
Swift 中函數(shù)可以返回一個函數(shù):
func myFuncThatReturnsAFunc() -> (Int) -> String {
return { number in
return "The lucky number is \(number)"
}
}
let returnedFunction = myFuncThatReturnsAFunc()
returnedFunction(5) // The lucky number is 5
為了更具有可讀性,你當然可以為你的返回函數(shù)定義一個別名:
typealias returnedFunctionType = (Int) -> String
func myFuncThatReturnsAFunc() -> returnedFunctionType {
return { number in
return "The lucky number is \(number)"
}
}
let returnedFunction = myFuncThatReturnsAFunc()
returnedFunction(5) // The lucky number is 5
如果在這篇文章中你沒對函數(shù)有足夠的體會,那么了解下 Swift 可以在函數(shù)中定義函數(shù)也是不錯的。
func myFunctionWithNumber(someNumber: Int) {
func increment(var someNumber: Int) -> Int {
return someNumber + 10
}
let incrementedNumber = increment(someNumber)
println("The incremeted number is \(incrementedNumber)")
}
myFunctionWithNumber(5)
// The incremeted number is 15
Swift 函數(shù)有更多的選項以及更為強大功能。從你開始利用 Swift 編程時,記?。耗芰υ綇娯熑卧酱?。請一定要巧妙地優(yōu)化可讀性!
Swift 的最佳實踐還沒被確立,這門語言也在不斷地進化,所以請朋友和同事來審查你的代碼。我發(fā)現(xiàn)一些從來沒見過 Swift 的人反而在我的 Swift 代碼中提供了很大幫助。
Swift 編碼快樂!