Swift3.1_函数与闭包

函数

当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为参数,也可以定义某种类型的值作为函数执行结束时的输出,称为返回类型。

每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,用函数名来调用这个函数,并传给它匹配的输入值实参。函数的实参必须与函数参数表里参数的顺序一致。

函数参数与返回值

函数的定义以func作为前缀,后接方法名,后跟一对括号定义参数,参数用,隔开;指定函数返回类型时,用返回箭头->后跟返回类型的名称的方式来表示。

无参数函数
func sayHelloWorld() -> String {
    return "hello, world"
}
print(sayHelloWorld())
// 打印 "hello, world"
多参数函数
func add(num1: Int, num2: Int) -> String {
    return "sum is \(num1+num2)"
}
print(add(num1: 3, num2: 5))
// 打印 "sum is 8"
无返回值函数
func say(message: String) {
    print("say \(message)")
}
say(message: "hello")
// 打印 say hello

严格上来说,虽然没有返回值被定义,say(message:)函数依然返回了值。没有定义返回类型的函数会返回一个特殊的Void值。它其实是一个空的元组tuple,没有任何元素,可以写成()

多重返回值函数

你可以用元组tuple类型让多个值作为一个复合值从函数中返回。

func minmax(array: [Int]) -> (min: Int, max: Int) {
    let min = array.min()!
    let max = array.max()!
    return (min, max)
}
let (min, max) = minmax(array: [2, 4, 9, 1])
print("min: \(min), max: \(max)")
// 打印 min: 1, max: 9
可选元组返回类型

如果函数返回的元组类型有可能整个元组都没有值,你可以使用可选的optional元组返回类型反映整个元组可以是nil的事实。

可选元组类型如(Int, Int)?与元组包含可选类型如(Int?, Int?)是不同的。可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。

func minmax(array: [Int]) -> (min: Int, max: Int)? {
    if array.count == 0 {
        return nil
    }
    let min = array.min()!
    let max = array.max()!
    return (min, max)
}
// 通过可选绑定取值
if let (min, max) = minmax(array: [2, 4, 9, 1]) {
    print("min: \(min), max: \(max)")
}

函数参数标签和参数名称

每个函数参数都有一个参数标签argument label以及一个参数名称parameter name。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。

func someFunction(firstParameterName: Int, secondParameterName: Int) {
    // firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
}
someFunction(firstParameterName: 1, secondParameterName: 2)

所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的函数标签能够使你的代码更具可读性。

指定参数标签

你可以在参数名称前指定它的参数标签,中间以空格分隔:

func greet(person: String, from hometown: String) -> String {
    return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印 "Hello Bill! Glad you could visit from Cupertino."
忽略参数标签

如果你不希望为某个参数添加一个标签,可以使用一个下划线_来代替一个明确的参数标签。

func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
     // firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
}
someFunction(1, secondParameterName: 2)
默认参数值

你可以在函数体中通过给参数赋值来为任意一个参数定义默认值Deafult Value。当默认值被定义后,调用这个函数时可以忽略这个参数。

func double(num: Int, multiple: Int = 2) -> Int {
    return num * multiple
}
print(double(num: 5))
// 打印 10
可变参数

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

func sum(_ numbers: Int...) -> Int {
    var sum = 0
    for num in numbers {
        sum = sum + num
    }
    return sum
}
print(sum(2, 3, 5))

注意:一个函数最多只能拥有一个可变参数。

输入输出参数

函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数In-Out Parameters

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temp = a;
    a = b
    b = temp
}
var a = 3
var b = 5
swapTwoInts(&a, &b)
print("a: \(a), b: \(b)")
// 打印 a: 5, b: 3

函数类型

每个函数都有种特定的函数类型,函数的类型由函数的参数类型和返回类型组成。

func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}

函数的类型是(Int, Int) -> Int

func printHelloWorld() {
    print("hello, world")
}

函数的类型是() -> Void

使用函数类型
var mathFunction: (Int, Int) -> Int = addTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"

addTwoIntsmathFunction有同样的类型,并让这个新变量指向addTwoInts函数”,就可以用mathFunction来调用被赋值的函数了。

函数类型作为参数类型
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// 打印 "Result: 8"
函数类型作为返回类型
func stepForward(_ input: Int) -> Int {
    return input + 1
}
func stepBackward(_ input: Int) -> Int {
    return input - 1
}

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}

嵌套函数

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

func getTwice(_ num: Int) -> () -> Int {
    let multiple = 2
    func twice () -> Int {
        return num * multiple
    }
    return twice
}

闭包

Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:

  • 利用上下文推断参数和返回值类型
  • 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
  • 参数名称缩写
  • 尾随闭包语法

闭包表达式

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

{ (parameters) -> returnType in
    statements
}

sorted(by:)方法为例,

let numbers = [3, 9, 5, 1, 7]

sorted(by:)方法接受一个闭包,排序闭包函数类型需为(Int, Int) -> Bool

func backward(_ num1: Int, _ num2: Int) -> Bool {
    return num1 > num2
}

然而,以这种方式来编写一个实际上很简单的表达式num1 > num2,确实太过繁琐了。对于这个例子来说,利用闭包表达式语法可以更好地构造一个内联排序闭包。

let backwardNums = numbers.sorted(by: {(num1, num2) -> Bool in
    num1 > num2
})
根据上下文推断类型

Swift可以推断其参数和返回值的类型,sorted(by:)方法被一个Int数组调用,因此其参数必须是(Int, Int) -> Bool类型的函数。这意味着参数和返回值类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断:

let backwardNums = numbers.sorted(by: {num1, num2 in return num1 > num2})
单表达式闭包隐式返回

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

let backwardNums = numbers.sorted(by: {num1, num2 in num1 > num2})
参数名称缩写

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

let backwardNums = numbers.sorted(by: {$0 > $1})
运算符方法

实际上还有一种更简短的方式来编写上面例子中的闭包表达式。SwiftInt类型定义了关于大于号>的实现,其作为一个函数接受两个Int类型的参数并返回Bool类型的值。而这正好与sorted(by:)方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift可以自动推断出你想使用大于号的字符串函数实现:

let backwardNums = numbers.sorted(by: >)

尾随闭包

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

func myFunc(completion: () -> Void) {
    completion()
}

不使用尾随闭包进行函数调用:

myFunc(completion: {
    print("hello world")
})

使用尾随闭包进行调用:

myFunc() {
    print("hello world")
}

如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把()省略掉:

myFunc {
    print("hello world")
}

值捕获

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

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

如果我们单独考虑嵌套函数incrementer(),会发现它有些不同寻常:

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

incrementer()函数并没有任何参数,但是在函数体内访问了 runningTotalamount变量。这是因为它从外围函数捕获了runningTotalamount变量的引用。捕获引用保证了runningTotalamount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal依旧存在。

let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30

Swift闭包可以在其被定义的上下文中捕获常量或变量,而Objective-C需要使用__block捕获上下文中常量或变量;所以闭包不同于block

闭包是引用类型

上面的例子中,`incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。

无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。

解决闭包引起的循环强引用

Swift有如下要求:只要在闭包内使用self的成员,就要用self.someProperty或者self.someMethod()(而不只是somePropertysomeMethod())。这提醒你可能会一不小心就捕获了self,造成循环强引用。

定义捕获列表

捕获列表中的每一项都由一对元素组成,一个元素是weakunowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如delegate = self.delegate!)。这些项在方括号中用逗号分开。

如果闭包有参数列表和返回类型,把捕获列表放在它们前面:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // 这里是闭包的函数体
}

如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方:

lazy var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // 这里是闭包的函数体
}

注意:

在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。

如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。

逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注@escaping,用来指明这个闭包是允许逃逸出这个函数的。

一种能使闭包逃逸出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:

var completionHandlers: [() -> Void] = []
func escapingClosureFunc(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

将一个闭包标记为@escaping意味着你必须在闭包中显式地引用self

自动闭包

自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。

自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。下面的代码展示了闭包如何延时求值。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出 "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"

你可能感兴趣的:(Swift3.1_函数与闭包)