31.4. 函数类型

函数类型 表示一个函数、方法或闭包的类型,它由形参类型和返回值类型组成,中间用箭头(->)隔开:(形参类型)->(返回值类型

形参类型 是由逗号间隔的类型列表。由于返回值类型 可以是元组类型,所以函数类型支持多返回值的函数与方法。

你可以对形参类型为 () -> T(其中 T 是任何类型)的函数使用 autoclosure 特性,这会在调用侧隐式创建一个闭包。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为形参的函数类型的例子详见 8.7. 自动闭包

函数类型可以拥有多个可变参数在形参类型 中。从语法角度上讲,可变参数由一个基础类型名字紧随三个点(...)组成,如 Int...。可变参数被认为是一个包含了基础类型元素的数组。即 Int... 就是 [Int]。关于使用可变参数的例子,请参阅 可变参数

为了指定一个 in-out 参数,可以在形参类型前加 inout 前缀。但是你不可以对可变参数或返回值类型使用 inout。关于这种形参的详细讲解请参阅 输入输出参数

如果函数类型只有一个类型是元组类型的一个形参,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说,((Int, Int)) -> Void 是接收一个元组 (Int, Int) 作为形参并且不返回任何值的函数类型。与此相对,不加括号的 (Int, Int) -> Void 是一个接收两个 Int 作为形参并且不返回任何值的函数类型。相似地,因为 Void 是空元组类型 () 的别名,函数类型 (Void)-> Void(()) -> () 是一样的 - 一个将空元组作为唯一实参的函数。但这些类型和 () -> () 是不一样的 - 一个无实参的函数。

函数和方法中的实参名并不是函数类型的一部分。例如:

func someFunction(left: Int, right: Int) {}
func anotherFunction(left: Int, right: Int) {}
func functionWithDifferentLabels(top: Int, bottom: Int) {}
var f = someFunction // 函数 f 的类型为 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void.
f = anotherFunction              // 正确
f = functionWithDifferentLabels  // 正确
func functionWithDifferentArgumentTypes(left: Int, right: String) {}
f = functionWithDifferentArgumentTypes     // 错误
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentNumberOfArguments // 错误

由于实参标签不是函数类型的一部分,你可以在写函数类型的时候省略它们。

var operation: (lhs: Int, rhs: Int) -> Int      // 错误
var operation: (_ lhs: Int, _ rhs: Int) -> Int  // 正确
var operation: (Int, Int) -> Int                // 正确

如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型 (Int) -> (Int) -> Int 可以理解为 (Int) -> ((Int) -> Int),也就是说,该函数传入 Int,并返回另一个传入并返回 Int 的函数。

函数类型若要抛出或重抛错误就必须使用 throws 关键字来标记。throws 关键字是函数类型的一部分,非抛出函数是抛出函数的子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 抛出函数与方法 和 重抛函数与方法。

对非逃逸闭包的限制

当非逃逸闭包函数是形参时,不能存储在属性、变量或任何 Any 类型的常量中,因为这可能导致值的逃逸。

当非逃逸闭包函数是形参时,不能作为实参传递到另一个非逃逸闭包函数中。这样的限制可以让 Swift 在编译时就完成更好的内存访问冲突检查,而不是在运行时。举个例子:

let external: (Any) -> Void = { _ in () }
func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) {
    first(first)    // 错误
    second(second)  // 错误
    first(second)   // 错误
    second(first)   // 错误
    first(external) // 正确
    external(first) // 正确
}

在上面代码里,takesTwoFunctions(first:second:) 的两个形参都是函数。它们都没有标记为 @escaping, 因此它们都是非逃逸的。

上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为形参 firstsecond 是非逃逸函数,它们不能够作为实参被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不会产生编译错误。这些函数调用不会违反限制,因为 external 不是 takesTwoFunctions(first:second:) 的形参之一。

如果你需要避免这个限制,标记其中一个形参为逃逸,或者使用 withoutActuallyEscaping(_:do:) 函数临时转换其中一个非逃逸函数形参为逃逸函数。关于避免内存访问冲突,可以参阅 第二十七章:内存安全

函数类型语法

function-type

函数类型 → 特性列表可选 函数类型子句 throws 可选 -> 类型

function-type-argument-clause

函数类型子句( ­ ) ­

函数类型子句( 函数类型实参列表 ... ­ 可选 )

function-type-argument-list

函数类型实参列表 → 函数类型实参 | 函数类型实参, 函数类型实参列表

function-type-argument

函数类型实参 → 特性列表可选 输入输出参数 可选 类型 | 实参标签 类型注解

argument-label

形参标签 → 标识符