Swift 类型
Swift 语言存在两种类型:命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义类 MyClass
的实例拥有类型 MyClass
。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 扩展 和 扩展声明 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。
复合型类型是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 (Int, (Int, Int))
包含两个元素:第一个是命名型类型 Int
,第二个是另一个复合型类型 (Int, Int)
。
你可以在命名型类型和复合型类型使用小括号。但是在类型旁加小括号没有任何作用。举个例子,(Int)
等同于 Int
。
本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。
type
类型语法
类型 → 函数类型
类型 → 数组类型
类型 → 字典类型
类型 → 类型标识
类型 → 元组类型
类型 → 可选类型
类型 → 隐式解析可选类型
类型 → 协议合成类型
类型 →不透明类型
类型 → 元型类型
类型 → 自身类型
类型 → Any
类型 → ( 类型 )
类型注解
类型注解显式地指定一个变量或表达式的类型。类型注解从冒号 (:
)开始, 以类型结尾,比如下面两个例子:
let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }
在第一个例子中,表达式 someTuple
的类型被指定为 (Double, Double)
。在第二个例子中,函数 someFunction
的形参 a
的类型被指定为 Int
。
类型注解可以在类型之前包含一个类型特性的可选列表。
类型注解语法
type-annotation
类型标识符
类型标识符可以引用命名型类型,还可引用命名型或复合型类型的别名。
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 Int
引用命名型类型 Int
,同样,类型标识符 Dictionary<String, Int>
引用命名型类型 Dictionary<String, Int>
。
在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 Point
来引用元组 (Int, Int)
:
typealias Point = (Int, Int)
let origin: Point = (0, 0)
情况二,类型标识符使用点语法(.
)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 ExampleModule
模块中声明的命名型类型 MyType
:
var someValue: ExampleModule.MyType
类型标识符语法
type-identifier
type-name
类型名称 → 标识符
元组类型
元组类型是使用括号括起来的零个或多个类型,类型间用逗号隔开。
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 (:)
组成。函数和多返回值 章节里有一个展示上述特性的例子。
当一个元组类型的元素有名字的时候,这个名字就是类型的一部分。
var someTuple = (top: 10, bottom: 12) // someTuple 的类型为 (top: Int, bottom: Int)
someTuple = (top: 4, bottom: 42) // 正确:命名类型匹配
someTuple = (9, 99) // 正确:命名类型被自动推断
someTuple = (left: 5, right: 5) // 错误:命名类型不匹配
所有的元组类型都包含两个及以上元素, 除了 Void
。Void
是空元组类型 ()
的别名。
元组类型语法
tuple-type
tuple-type-element-list
tuple-type-element
element-name
元素名 → 标识符
函数类型
函数类型表示一个函数、方法或闭包的类型,它由形参类型和返回值类型组成,中间用箭头(->
)隔开:
(
形参类型
)->(返回值类型
)
形参类型是由逗号间隔的类型列表。由于返回值类型可以是元组类型,所以函数类型支持多返回值的函数与方法。
你可以对形参类型为 () -> T
(其中 T 是任何类型)的函数使用 autoclosure
特性,这会在调用侧隐式创建一个闭包。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为形参的函数类型的例子详见 自动闭包。
函数类型可以拥有多个可变参数在形参类型中。从语法角度上讲,可变参数由一个基础类型名字紧随三个点(...
)组成,如 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
关键字是函数类型的一部分,非抛出函数是抛出函数的子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 抛出函数与方法 和 重抛函数与方法。
异步函数的函数类型必须使用 async
关键字来标记。 async
关键字也是函数类型的一部分,且同步函数是异步函数的子类型。因此,在使用异步函数的地方也可以使用同步函数。异步函数的相关描述见章节 异步函数和方法。
对非逃逸闭包的限制
当非逃逸闭包函数是形参时,不能存储在属性、变量或任何 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
function-type-argument-clause
函数类型子句 → **(** **)**
函数类型子句 → ( 函数类型实参列表 *...* 可选 )
function-type-argument-list
function-type-argument
argument-label
形参标签 → 标识符
数组类型
Swift 语言为标准库中定义的 Array<Element>
类型提供了如下语法糖:
[
类型
]
换句话说,下面两个声明是等价的:
let someArray: Array<String> = ["Alex", "Brian", "Dave"]
let someArray: [String] = ["Alex", "Brian", "Dave"]
上面两种情况下,常量 someArray
都被声明为字符串数组。数组的元素也可以通过下标访问:someArray[0]
是指第 0 个元素 "Alex"
。
你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组:
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]