swift学习笔记2——函数、闭包

之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习、总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询用。详细可以参考the-swift-programming-language-in-chinese,或者苹果官方英文版文档

当前版本是swift2.2

函数

func sayHello(personName: String, alreadyGreeted: Bool) -> String {
    if alreadyGreeted {
        return sayHelloAgain(personName)
    } else {
        return sayHello(personName)
    }
}

函数参数名称(Function Parameter Names)

函数参数都有一个外部参数名(external parameter name)和一个局部参数名(local parameter name)。外部参数名用于在函数调用时标注传递给函数的参数,局部参数名在函数的实现内部使用。

func someFunction(firstParameterName: Int, secondParameterName: Int) {
    // function body goes here
    // firstParameterName and secondParameterName refer to
    // the argument values for the first and second parameters
}
someFunction(1, secondParameterName: 2)

一般情况下,第一个参数省略其外部参数名,第二个以及随后的参数使用其局部参数名作为外部参数名。所有参数必须有独一无二的局部参数名。尽管多个参数可以有相同的外部参数名,但不同的外部参数名能让你的代码更有可读性。

你可以在局部参数名前指定外部参数名,中间以空格分隔:

func someFunction(externalParameterName localParameterName: Int) {
    // function body goes here, and can use localParameterName
    // to refer to the argument value for that parameter
}

如果你提供了外部参数名(包括局部参数名作为外部参数名的情况),那么函数在被调用时,必须使用外部参数名。如果想忽略外部参数名,使用_替代即可

默认参数值(Default Parameter Values)

你可以在函数体中为每个参数定义默认值(Deafult Values)。当默认值被定义后,调用这个函数时可以忽略这个参数。

func someFunction(parameterWithDefault: Int = 12) {
}
someFunction(6) // parameterWithDefault is 6
someFunction() // parameterWithDefault is 12

将带有默认值的参数放在函数参数列表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。不过不放在最后也是可以的

可变参数(Variadic Parameters)

一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(...)的方式来定义可变参数。

可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 numbers 的 Double... 型可变参数,在函数体内可以当做一个叫 numbers 的 [Double] 型的数组常量。

// 求算数平均数
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)

// 使用AnyObject就类似print函数了
func alvalible(num:AnyObject...) {
    print(num[0],num[1])
    
}

常量参数和变量参数

函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。

通过在参数名前加关键字 var 来定义变量参数:

func alignRight(var astring: String) -> String {
    astring += "123"
    return astring
}

swift是值传递的,可变参数作用域只在函数内部,对调用者不产生影响,和C中的指针传递不一样,如果想达到C指针的效果,可以使用输入输出参数

输入输出参数

变量参数,正如上面所述,仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。调用的时候实参必须可变,且加上&

func swapTwoInts(inout a: Int, inout _ b: Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)

输入输出参数不能有默认值,而且可变参数不能用 inout 标记。如果你用 inout 标记一个参数,这个参数不能被 var 或者 let 标记。

函数参数

func addTwoInts(a: Int, _ b: Int) -> Int {
    return a + b
}
func printHelloWorld() {
    print("hello, world")
}

第一个函数的类型是(Int, Int) -> Int,可以解读为“这个函数类型有两个 Int 型的参数并返回一个 Int 型的值。”。第二个() -> Void

在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:

var mathFunction: (Int, Int) -> Int = addTwoInts

mathFunction(2, 3) // 直接使用

这个可以解读为:

“定义一个叫做 mathFunction 的变量,类型是‘一个有两个 Int 型的参数并返回一个 Int 型的值的函数’,并让这个新变量指向 addTwoInts 函数”。

就像其他类型一样,当赋值一个函数给常量或变量时,你可以让 Swift 来推断其函数类型:

let anotherMathFunction = addTwoInts

参数为函数类型

func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \\(mathFunction(a, b))")
}

函数类型作为返回类型

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
}

嵌套函数

嵌套函数(Nested Functions)

这章中你所见到的所有函数都叫全局函数(global functions),它们定义在全局域中。你也可以把函数定义在别的函数体中,称作嵌套函数(nested functions)。

默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(被嵌套的函数即例中的chooseStepFunction)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。

你可以用返回嵌套函数的方式重写 chooseStepFunction(_???? 函数:

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)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!

闭包

闭包表达式语法有如下一般形式:

{ (parameters) -> returnType in
    statements
}

闭包表达式语法可以使用常量、变量和inout类型作为参数,不能提供默认值。也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值。

sort(_:)方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true,反之返回false。sort函数会将数组里面的元素按照冒泡排序一样的返回给s1,s2两个参数,然后进行比较排序。

函数对应的闭包表达式版本的代码:

reversed = names.sort({ (s1: String, s2: String) -> Bool in
    return s1 > s2
})

然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。

闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。

因为排序闭包函数是作为sort(_:)方法的参数传入的,Swift 可以推断其参数和返回值的类型。sort(_:)方法被一个字符串数组调用,因此其参数必须是(String, String) -> Bool类型的函数。这意味着(String, String)和Bool类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:

reversed = names.sort( { s1, s2 in return s1 > s2 } )

实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,都可以推断出闭包的参数和返回值类型。 这意味着闭包作为函数或者方法的参数时,您几乎不需要利用完整格式构造内联闭包。

单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

reversed = names.sort( { s1, s2 in s1 > s2 } )

在这个例子中,sort(_:)方法的第二个参数函数类型明确了闭包必须返回一个Bool类型值。因为闭包函数体只包含了一个单一表达式(s1 > s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略。

参数名称缩写

Swift 自动为内联闭包提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数,以此类推。

尾随闭包(Trailing Closures)

如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用:

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}

在闭包表达式语法一节中作为sort(_:)方法参数的字符串排序闭包可以改写为:

reversed = names.sort() { $0 > $1 }

如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉:

reversed = names.sort { $0 > $1 }

map(_:)函数,它接受一个具有一个参数和一个返回值的闭包,它会遍历sortArr数组的每一个元素并将其赋给闭包参数,闭包的返回值将组成一个新的数组作为map函数的最终返回。新生产的数组元素与之前的数组一一对应

let sortArr = [1,2,3,4]
let strArr = sortArr.map{ (temp) -> String in   // 结果 strArr = ["4", "5", "6", "7"]
    return "\(temp + 3)"
}
             

捕获值(Capturing Values)

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。可以在参数名之前标注@noescape,用来指明这个闭包是不允许“逃逸”出这个函数的,即这个函数执行完成之后闭包就被释放了。如果强制给一个不允许“逃逸”的闭包赋值给全局变量则会编译错误

将闭包作为参数传递给函数时,你能获得同样的延时求值行为。

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
    print("Now serving \\(customerProvider())!")
}
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"

serveCustomer(_:)接受一个返回顾客名字的显式的闭包。下面这个版本的serveCustomer(_:)完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为@autoclosure来接收一个自动闭包。现在你可以将该函数当做接受String类型参数的函数来调用。customerProvider参数将自动转化为一个闭包,因为该参数被标记了@autoclosure特性。

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serveCustomer(@autoclosure customerProvider: () -> String) {
    print("Now serving \\(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))
// prints "Now serving Ewa!"

@autoclosure特性暗含了@noescape特性,如果你想让这个闭包可以“逃逸”,则应该使用@autoclosure(escaping)特性.