善變的Swift函數

原文連接:http://www.objc.io/issue-16/swift-functions.htmlhtml

前言

儘管OC跟其餘語言有些很奇形怪狀的語法,可是一旦你找到竅門方法語法仍是至關直截了當的。快速回顧下:ios

+ (void)mySimpleMethod
{
    // class method
    // no parameters 
    // no return values
}

- (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2
{
    // instance method
    // one parameter of type NSString pointer, one parameter of type NSNumber pointer
    // must return a value of type NSString pointer
    return @"hello, world!";
}

對比下,Swift的語法看上去更像另一種編程語言,可能比OC更復雜更容易混淆。express

在我繼續以前,我想澄清下Swift方法與函數的區別,由於我會通篇文章地使用它們。這裏有個方法的定義,根據蘋果官方教程:編程

Methods are functions that are associated with a particular type. Classes, structures, and enumerations can all define instance methods, which encapsulate specific tasks and functionality for working with an instance of a given type. Classes, structures, and enumerations can also define type methods, which are associated with the type itself. Type methods are similar to class methods in Objective-C.json

函數是獨立的,而方法是函數在類、結構體、枚舉中的封裝。swift

解剖Swift函數

舉個Swift"Hello,World!"的栗子:數組

func mySimpleFunction() {
    println("hello, world!")
}

若是你還用過除了OC以外的語言編程過,那麼上面的函數會很熟悉哦。app

  • func關鍵字代表這是個函數
  • 函數名是mySimpleFunction
  • 沒有傳參 - 空參
  • 沒有返回值
  • 函數體執行域在{}內

如今咱們整個稍微複雜點的函數:框架

func myFunctionName(param1: String, param2: Int) -> String {
    return "hello, world!"
}

該函數帶一個叫param1字符串型和另外一個叫param2整型的參數,返回字符串。dom

調用全部的函數

Swift與OC之間最大得一個差異是在Swift函數參數怎麼工做的。若是你跟我同樣喜歡冗長的OC,當Swift函數被調用的時候默認參數名是沒有一塊兒跟進來攪和的。

func hello(name: String) {
    println("hello \(name)")
}

hello("Mr. Roboto")

這看上去還不壞知道你爲你的函數添加了其餘的參數:

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")

若是碰到上面的狀況,你要分清楚每一個參數究竟是什麼就很頭疼了。。

在Swift中,有一個概念是外部參數名來澄清混淆:

func hello(fromName name: String) {
    println("\(name) says hello to you!")
}

hello(fromName: "Mr. Roboto")

上述函數中,fromName是一個外部的參數,當函數被調用的時候被攪和進去了,可是name內部參數用來引用函數執行時候內部的參數。

若是你想內外參數命名一致的話,你不用重複寫參數名。。:

func hello(name name: String) {
    println("hello \(name)")
}

hello(name: "Robot")

而是,只要捷徑地添加一個#放在參數名前面:

func hello(#name: String) {
    println("hello \(name)")
}

hello(name: "Robot")

固然,這個規則跟方法中參數怎麼工做仍是有細微得區別的。。。

調用方法

當封裝到一個類中,方法的第一個參數名不被外部包含,而接下來全部得參數名在方法調用的時候所有對外包含。

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")

所以這是你最佳實踐來包含你的第一個參數名字到你的方法名(使用With),就像OC同樣:

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")

不是調用個人函數「hello」,而是從新命名了函數名爲helloWithName來肯定第一個參數是一個姓名。

若是由於某些特殊的緣由讓你想在函數中忽略外部參數名字(不建議),你可使用_做爲外部參數名字:

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中實例方法事實上是柯里化函數:

The basic idea behind currying is that a function can be partially applied, meaning that some of its parameter values can be specified (bound) before the function is called. Partial function application yields a new function.

那麼假設我有一個類:

class MyHelloWorldClass {

    func helloWithName(name: String) -> String {
        return "hello, \(name)"
    }
}

我能夠建立一個變量指向helloWithName這個函數:

let helloWithNameFunc = MyHelloWorldClass.helloWithName
// MyHelloWorldClass -> (String) -> String

個人新函數helloWithNameFunc屬於MyHelloWorldClass -> (String) -> String類型,即一個函數帶了我自定義類的一個實例做爲參數而且返回另外一個函數(這個函數帶一個字符串參數而且返回一個字符串值)。

故實際上我能夠這麼調用個人函數:

let myHelloWorldClassInstance = MyHelloWorldClass()

helloWithNameFunc(myHelloWorldClassInstance)("Mr. Roboto") 
// hello, Mr. Roboto

Init:特別注意

在一個類,結構體,枚舉初始化的時候特殊的init方法會被調用。在Swift中,你能夠定義初始化參數,就像其餘方法那樣:

class Person {

    init(name: String) {
        // your init implementation
    }

}

Person(name: "Mr. Roboto")

注意到不像其餘方法,init方法在實例化的時候其第一個參數須要外部參數名字的。

這種時候最佳實踐是添加一個可區分的外部名字-來加強初始化的可讀性:

class Person {

    init(fromName name: String) {
        // your init implementation
    }

}

Person(fromName: "Mr. Roboto")

固然,就跟其餘方法同樣,你能夠添加一個_若是你想讓你的初始化方法忽略外部參數名字。舉個官方栗子:

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 is 100.0

let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0

忽略外部參數在你若是想抽象化類/枚舉/結構體的時候一樣頗有用。再舉個某大師的栗子:

public struct JSValue : Equatable {

    // ... truncated code

    /// Initializes a new `JSValue` with a `JSArrayType` value.
    public init(_ value: JSArrayType) {
        self.value = JSBackingValue.JSArray(value)
    }

    /// Initializes a new `JSValue` with a `JSObjectType` value.
    public init(_ value: JSObjectType) {
        self.value = JSBackingValue.JSObject(value)
    }

    /// Initializes a new `JSValue` with a `JSStringType` value.
    public init(_ value: JSStringType) {
        self.value = JSBackingValue.JSString(value)
    }

    /// Initializes a new `JSValue` with a `JSNumberType` value.
    public init(_ value: JSNumberType) {
        self.value = JSBackingValue.JSNumber(value)
    }

    /// Initializes a new `JSValue` with a `JSBoolType` value.
    public init(_ value: JSBoolType) {
        self.value = JSBackingValue.JSBool(value)
    }

    /// Initializes a new `JSValue` with an `Error` value.
    init(_ error: Error) {
        self.value = JSBackingValue.Invalid(error)
    }

    /// Initializes a new `JSValue` with a `JSBackingValue` value.
    init(_ value: JSBackingValue) {
        self.value = value
    }
}

不思議的參數

在Swift中,可選類型有一個新的概念定義:

選項參數類型

Optionals say either 「there is a value, and it equals x」 or 「there isn’t a value at all.」 Optionals are similar to using nil with pointers in Objective-C, but they work for any type, not just classes. Optionals are safer and more expressive than nil pointers in Objective-C and are at the heart of many of Swift’s most powerful features.

爲了說明一個參數類型多是可選的,只須要其後追加一個問號:

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")
// The optional has a value! It's someString

myFuncWithOptionalType(nil)
// The optional is nil

當你作過OC開發,對比下選項類型毫無疑問節省了開發時間。

帶默認值的參數

func hello(name: String = "you") {
    println("hello, \(name)")
}

hello(name: "Mr. Roboto")
// hello, Mr. Roboto

hello()
// hello, you

注意到有默認值的參數自動有一個外部參數名字。

而且由於函數被調用的時候參數帶默認值能夠被忽略,最佳實踐是把你帶默認值的參數放在整個函數參數列表的末尾。根據官方文檔智力須要注意一點:

Place parameters with default values at the end of a function’s parameter list. This ensures that all calls to the function use the same order for their non-default arguments, and makes it clear that the same function is being called in each case.

做者是帶默認參數的粉絲,大部分是由於它增長代碼可變性以及日後兼容性。你能夠從特定的情形帶兩個參數開始,好比一個函數配置一個自定義的UITableViewCell,而且若是另外一個情形出現須要另一個參數(好比label要不一樣的顏色)-而該函數其餘全部得地方都是不須要改變的,那你代碼中僅須要改變的是你只須要傳第一個非默認的參數。

可變數量參數

可變數量參數相比傳個數組而言是簡單易讀懂版本。實際上,若是你看完了上面外部參數名字的栗子,你將看出它是字符串數組類型:

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,就至關於傳進去一個空數組,因此不要忘記作判斷(做者說是須要時候,我的以爲仍是都無腦判斷下):

func helloWithNames(names: String...) {
    if names.count > 0 {
        for name in names {
            println("Hello, \(name)")
        }
    } else {
        println("Nobody here!")
    }
}

helloWithNames()
// Nobody here!

可傳入可傳出參數(參數引用)

涉及到可傳入可傳出參數的時候你(參數引用),你能夠對外部變量進行控制:

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

這是很是常見OC處理異常的場景,舉個NSJSONSerialization栗子:

- (void)parseJSONData:(NSData *)jsonData
{
    NSError *error = nil;
    id jsonResult = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];

    if (!jsonResult) {
        NSLog(@"ERROR: %@", error.description);
    }
}

// 由於Swift還算新,沒有關於錯誤處理的正式說明,可是這裏有關於傳入傳出參數以外的選擇!

泛型參數

直接舉個栗子:

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

更多泛型章節請戳這裏

可修改的參數

默認狀況下,帶到函數裏面的參數是常量,因此它們在函數域內是不能被操做的。若是你想改變這樣的狀況,只要在你的參數前加上var關鍵字:

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作下對比,可修改參數並無修改外部傳進來的參數哦!

把函數做爲參數

在Swift中,函數能夠被當作參數傳遞。舉個例子,一個函數能夠把另一個函數當作參數:

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

注意到函數引用被傳遞了進來 - 這個栗子中是defaultLotteryHandler。這個函數被做爲接收到的函數後續被執行。

實例方法一樣也能夠這麼玩:

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

固然爲了讓你得函數定義更具備可讀性,請考慮使用typealias起別名。

typealias lotteryOutputHandler = (String, Int) -> String

func luckyNumberForName(name: String, #lotteryHandler: lotteryOutputHandler) -> String {
    let luckyNumber = Int(arc4random() % 100)
    return lotteryHandler(name, luckyNumber)
}

你也是用匿名函數做爲函數參數(至關OC中的BLOCK)

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

在OC中,在遇到編寫異步完成或者錯誤處理句柄時候很流行使用block作參數。一樣在Swift中也能這麼玩。

訪問控制

Swift中有3種級別的訪問控制:

  • 公共訪問(Public access)在該module中全部源文件均可以訪問,其餘module定義了這個module的話也可使用。這種通常是框架級別的定義。
  • 內部訪問(Internal access)非該module的文件沒法訪問應用級別的定義或者框架內部結構的定義。
  • 私有訪問(Private access)只有定義源文件才能訪問,使用這個級別能夠隱藏不想公開的實現細節。

默認狀況下,函數跟變量是內部的-若是你想作出改變,那麼你不得不在方法和變量以前追加private or public關鍵字:

public func myPublicFunc() {

}

func myInternalFunc() {

}

private func myPrivateFunc() {

}

private func myOtherPrivateFunc() {

}

來自Ruby的習慣,做者更喜歡吧他全部得私有方法寫在類的底部,用註釋隔開(做者吹牛逼)

class MyFunClass {

    func myInternalFunc() {

    }

    // MARK: Private Helper Methods

    private func myPrivateFunc() {

    }

    private func myOtherPrivateFunc() {

    }
}

這裏做者跟大蘋果提出了建議。。。略一句

不思議的返回類型

Swift的返回值顯然比OC複雜多了,特別是可選類型與多值類型。

可選類型返回值

你那有這樣的狀況嗎:你的函數能夠返回空值,你須要去確認返回值類型是不是可選的:

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")
}

做者看過最好關於可選類型的解釋(是否是偏僻入裏,意會不言傳):

I finally get @SwiftLang optionals, they are like Schrödinger’s cat! You have to see if the cat is alive before you use it.

多值返回類型

Swift最讓人雞凍的特性是能夠返回多值:

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)

正如你所見,上述返回了一組值。這裏有兩種方式來使用組值。

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

多值返回混雜可選

當多值返回中返回值中夾雜這可選類型是須要技巧的,這裏介紹兩種處理方式。

在上述的栗子函數,邏輯是存在瑕疵的 - 有可能出現空值傳入,那麼咱們程序可能會崩潰。若是沒有值傳入,那麼我得將返回值設置成可選類型:

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

正如你所見,這不是你OC的處理模式。

函數做爲返回值

Swift中一個函數能夠返回值能夠是另外一個函數:

func myFuncThatReturnsAFunc() -> (Int) -> String {
    return { number in
        return "The lucky number is \(number)"
    }
}

let returnedFunction = myFuncThatReturnsAFunc()

returnedFunction(5) // The lucky number is 5

咳咳,仍是得用typealias關鍵字增長可讀性:

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

嵌套函數

若是上述的知識尚未餵飽你的話,這裏還有一點就是在Swift中容許函數嵌套:

func myFunctionWithNumber(someNumber: Int) {

    func increment(var someNumber: Int) -> Int {
        return someNumber + 10
    }

    let incrementedNumber = increment(someNumber)
    println("The incremented number is \(incrementedNumber)")
}

myFunctionWithNumber(5)
// The incremented number is 15

總結

Happy Swifting! 快樂編程!

相關文章
相關標籤/搜索