函数类型 表示一个函数、方法或闭包的类型,它由形参类型和返回值类型组成,中间用箭头(->
)隔开:(形参类型
)->(返回值类型
)
形参类型 是由逗号间隔的类型列表。由于返回值类型 可以是元组类型,所以函数类型支持多返回值的函数与方法。
你可以对形参类型为 () -> 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
, 因此它们都是非逃逸的。
上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为形参 first
和 second
是非逃逸函数,它们不能够作为实参被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不会产生编译错误。这些函数调用不会违反限制,因为 external
不是 takesTwoFunctions(first:second:)
的形参之一。
如果你需要避免这个限制,标记其中一个形参为逃逸,或者使用 withoutActuallyEscaping(_:do:)
函数临时转换其中一个非逃逸函数形参为逃逸函数。关于避免内存访问冲突,可以参阅 第二十七章:内存安全 。
函数类型语法
function-type
函数类型 → 特性列表可选 函数类型子句 throws 可选 -> 类型
function-type-argument-clause
函数类型子句 → ( )
函数类型子句 → ( 函数类型实参列表 ... 可选 )
function-type-argument-list
函数类型实参列表 → 函数类型实参 | 函数类型实参, 函数类型实参列表
function-type-argument
函数类型实参 → 特性列表可选 输入输出参数 可选 类型 | 实参标签 类型注解
argument-label
形参标签 → 标识符