函数
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为参数,也可以定义某种类型的值作为函数执行结束时的输出,称为返回类型。
每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,用函数名来调用这个函数,并传给它匹配的输入值实参
。函数的实参必须与函数参数表里参数的顺序一致。
函数参数与返回值
函数的定义以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"
addTwoInts
和mathFunction
有同样的类型,并让这个新变量指向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})
运算符方法
实际上还有一种更简短的方式来编写上面例子中的闭包表达式。Swift
的Int
类型定义了关于大于号>
的实现,其作为一个函数接受两个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()
函数并没有任何参数,但是在函数体内访问了 runningTotal
和amount
变量。这是因为它从外围函数捕获了runningTotal
和amount
变量的引用。捕获引用保证了runningTotal
和amount
变量在调用完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()
(而不只是someProperty
或someMethod()
)。这提醒你可能会一不小心就捕获了self
,造成循环强引用。
定义捕获列表
捕获列表中的每一项都由一对元素组成,一个元素是weak
或unowned
关键字,另一个元素是类实例的引用(例如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"
网友评论