类型
Swift 存在着两种类型:命名类型和复合类型。 命名类型 是一种在定义时可以赋予特有名称的类型。 命名类型包括类、结构体、枚举和协议。例如,一个命名为 MyClass
用户定义的实体,其类型为 MyClass
。 除了用户定义的命名类型外, Swift 标准库定义了许多常用的命名类型,包括那些表示数组、字典和可选值的类型。
那些通常被其它语言认为是基本或原始的数据类型 --- 比如表示数字、字符和字符串的类型 --- 实际上就是命名类型, 这些类型在 Swift 标准库里都是用的结构体来定义和实现的。因为它们是命名类型,所以可以根据需要扩展其的行为,关于如何使用扩展声明,可以查看 扩展 和 扩展声明 。
在 Swift 语言本身中,复合类型是一种没有名称和定义的类型。 Swift 存在两种复合类型: 函数类型和元组类型。 复合类型可以包含命名类型和其他复合类型。例如, (Int, (Int, Int))
类型包含两个元素:第一个是命名类型 Int
,第二个是其他的复合类型 (Int, Int)
。
你可以在单个命名类型或复合类型两边加小括号。但是,添加的小括号完全没有作用。例如, (Int)
完全等价于 Int
。
本节讨论 Swift 语言本身定义的类型,并描述 Swift 的类型推断行为。
类型语法
类型 → 数组类型
类型 → 字典类型
类型 → 函数类型
类型 → 类型标识
类型 → 元组类型
类型 → 可选类型
类型 → 隐式解析可选类型
类型 → 合成协议类型
类型 → 元类型
类型 →
(
类型)
类型注解#
类型注解 明确地指定了一个变量或者表达式的类型。就像下面例子中展示的那样,类型注解始于冒号 (:
) 结于类型:
let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }
在第一个例子中,表达式 someTuple
被指定为属于元组类型 (Double, Double)
。 在第二个例子中, 函数 someFunction
中的参数 a
被指定为属于类型 Int
。
类型注解可以在类型之前包含一个类型特性的可选列表。
类型注解的语法
类型标识符#
类型标识符是指命名类型,也可以指命名或复合类型的类型别名。
大多数时候,类型标识符直接引用与命名类型相同的名字作为标识符。
例如类型标识符 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
类型标识符语法
类型标识符 → 类型名称 泛型参数子句 可选 | 类型名称 泛型参数子句 可选
.
类型标识符类型名称 → 标识符
元组类型#
元组类型是用圆括号括起来的一系列用逗号分隔符分割开的类型。
你可以使用元组类型作为函数的返回类型,这样就可以使一个函数能够返回多个值。你也可以对元组类型中的元素进行命名,然后用这些名称引用单个元素的值。元素的名称由标识符和后面紧跟着的冒号( :
)组成。 这里有个例子讲述了这些特性,参看 多返回值函数 。
当一个元组中的元素拥有名称时,其名称也是类型的一部分。
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
是一个空元组类型 ()
的类型别名,所有的元组类型都包含两个或两个以上的类型。
元组类型语法
元组类型 →
(
)
|(
元组类型元素,
元组类型元素列表)
元组类型元素列表 → 元组类型元素 | 元组类型元素
,
元组类型元素列表元素名称 → 标识符
函数类型#
函数类型指的是函数、方法或者闭包的类型,它们由箭头( ->
)分隔开的参数和返回值类型构成:
(参数类型) -> 返回类型
参数类型 是被逗号分隔开的类型列表。因为 返回类型 可以是元组类型,所以函数类型支持函数和方法返回多个值。
函数类型 () -> T
(当 T
是任意类型) 的参数,为了在它被调用的时候隐式创建闭包,可以申请 自动闭包
特征。这提供了在语法结构上的简便方法,用于推迟表达式的执行, 并在调用函数时无需写显示闭包。关于自动闭包函数类型参数的例子,参看 自动闭包 。
函数类型在它的 参数类型 中可以拥有可变参数。在语法结构上,可变参数由基础类型名紧跟着三个点( ...
),例如 Int...
。可变参数可以认为是包含了基础类型元素的数组。比如可变参数 Int...
可以认为是 [Int]
。关于使用可变参数的例子,参看 可变参数。
给参数类型加上 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
关键字才可以抛出异常 ,函数类型必须包括 rethrows
关键字才可以重抛异常 。关键字 Throws
是函数类型的一部分, 且无抛出函数是抛出类型的子类型。因此,在相同的地方你可以使用无抛出函数作为一个抛出函数。抛出和重复抛出 函数的讨论在 抛出函数和方法 和 重抛函数和方法 。
非逃逸闭包的限制#
因为非逃逸函数的参数可以允许值进行逃逸,所以不能被存储于属性、变量或 Any
类型的常量中。
一个是非逃逸函数的参数,不能作为内容传递给其它非逃逸函数的参数。这个限制帮助 Swift 在编译时完成更多关于内存冲突的检查,而不是在运行时。 比如:
let external: (() -> Void) -> Void = { _ in () }
func takesTwoFunctions(first: (() -> Void) -> Void, second: (() -> Void) -> Void) {
first { first {} } // 错误
second { second {} } // 错误
first { second {} } // 错误
second { first {} } // 错误
first { external {} } // 正确
external { first {} } // 正确
}
在上面的代码中,takesTwoFunctions(first:second:)
中的两个参数都是函数。两个参数都没有用 @escaping
标记,所以结果他们都是非逃逸的。
在上例中标记为 错误
的 4 个函数会引起编译器异常。因为 first
和 second
参数是非逃逸函数,所以他们不能作为内容传递给其它非逃逸函数的参数。相反,标记为 正确
的两个函数不会引起编译错误。这两个函数没有违反限制,是因为 external
不是 takesTwoFunctions(first:second:)
的参数之一。
如果你需要避免这个限制,你可以将其中一个参数标记为逃逸,或者使用 withoutActuallyEscaping(_:do:)
函数临时将其中一个非逃逸函数参数转换为逃逸函数。避免内存冲突的资料,参看 内存安全 。
函数类型的语法
函数类型 → 特征 可选 函数类型参数子句
throws
可选->
类型函数类型 → 特征 可选 函数类型参数子句
rethrows
->
类型函数类型参数子句 →
(
函数类型参数列表...
可选)
函数类型参数列表 → 函数类型参数 | 函数类型参数
,
函数类型参数列表函数类型参数 → 特征 可选
inout
可选 类型 | 参数标签 类型注解参数标签 → 标识符
数组类型#
Swift 语言为 Swift 标准库的 Array<Element>
类型提供如下语法糖:
[type]
换句话说,下面的两种申明是等效的:
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]]]
当访问三维数组中的元素时,最左边的下标索引指向最外层数组中该索引对应的元素。 下一个右边的下标索引指向嵌套在同一层的数组中的所用对应的元素。诸如此类。也就是说在上面的例子中, array3D[0]
指向的元素为 [[1, 2], [3, 4]]
, array3D[0][1]
指向的元素 [3, 4]
, array3D[0][1][1]
指向的元素为 4 。
获取 Swift 标准库的 数组
类型的更多详细信息,参考 数组.
数组类型的语法
数组类型 →
[
类型]
字典类型#
Swift 语言为 Swift 标准库中的 字典<键, 值>
类型,提供如下语法糖:
[键 类型: 值 类型]
也就是说,下面的两种申明方式是等价的:
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
在这两种情况下,常量 someDictionary
被申明为一个以字符串作为键,整型作为值的字典。
可以通过在方括号中指定相应的键来访问字典中对应的值: someDictionary["Alex"]
指向键 Alex
关联的值。通过角标访问字典,返回的字典的值的类型是一个可选的值。如果字典中没有包含指定的键,就会返回 nil
。
字典的键的类型必须遵循 Swift 标准库的 可哈希化
协议。
了解更多 字典
类型的详细信息,请参考 字典.
字典类型的语法
可选类型#
在 Swift 标准库中 Swift 语言为 Optional<Wrapped>
类型定义了一种语法糖格式:?
后缀。也就是说,下面的两种申明方式是等价的:
var optionalInteger: Int?
var optionalInteger: Optional<Int>
在这两种情况下,optionalInteger
变量被申明为可选整型类型。注意在类型和 ?
之间没有空格。
Optional<Wrapped>
是一个有两种情况的枚举,none
和 some(Wrapped)
,代表有值或者没有值。 任何类型都可以显示的声明(或隐士的转换)为可选类型。如果你申明一个可选的变量或属性而没有为其赋初值,其值默认为 nil
。
如果一个可选类型的实例包含一个值,你可以使用后缀操作符 !
来进行访问,如下所示:
optionalInteger = 42
optionalInteger! // 42
使用 !
操作符去解包一个可选类型,如果值为 nil
会导致运行时错误。
你可以使用可选连接和可选绑定去有条件执行可选表达式。如果值为 nil
,不执行任何操作因此就不会产生运行时错误。
更多关于可选类型的信息和用例,请参考 可选类型.
可选类型语法
可选类型 → 类型
?
隐式解析可选类型#
Swift 语言定义后缀 !
作为 Swift 标准库定义的 Optional<Wrapped>
类型的简写,当它被访问时会自动进行解析。如果你使用的隐式解析可选的值为 nil
,将会产生运行错误。除了隐式解析行为外,下面的两种声明方式是等价的:
var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>
注意在类型和 !
之间没有空格。
因为隐式解析改变了他内部声明的类型的含义,可选类型嵌入在元组或泛型内 --- 例如字典或数字的元素类型 ----- 无法被标记为隐式解析。例如:
let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // Error
let implicitlyUnwrappedTuple: (Int, Int)! // OK
let arrayOfImplicitlyUnwrappedElements: [Int!] // Error
let implicitlyUnwrappedArray: [Int]! // OK
因为隐式解析可选和可选值具有相同的 Optional<Wrapped>
类型,在你使用可选的地方同样可以使用隐式解析可选。例如,你可将隐式解析可选的值赋值给变量、常量、可选属性,反之亦然。
有了可选,当你声明一个隐式解析可选变量或属性而没有为其赋初值,它的值默认为 nil
。
使用可选链会选择性的执行隐式解析可选表达式的某一个操作。如果值为 nil
,不会执行任何操作因此也不会产生任何运行错误。
关于隐式解析可选类型的更多细节,请参考 隐式解析可选.
隐式解析可选类型的语法
隐式 - 解析 - 可选 - 类型 → 类型
!
协议组合类型#
协议组合类型指的是遵循一个特定协议列表中每一个协议的类型,或者是一个给定的类的子类并遵循特定协议列表里的每一个协议。协议组合类型只能用在类型注释中指定类型,泛型参数子句以及通用 where
子句中。
协议组合类型有下列形式:
Protocol 1 & Protocol 2
协议组合类型允许你指定一个其类型遵循多个协议要求的值,而无需显式定义你想要符合的每个协议继承而来的新的命名协议。例如,你可以使用协议组合类型 ProtocolA & ProtocolB & ProtocolC
代替声明一个继承自 ProtocolA
,ProtocolB
和 ProtocolC
的新协议。同样,你可以使用 SuperClass & ProtocolA
而无需声明一个新的协议,它是 SuperClass
的子类并遵循 ProtocolA
。
协议组合列表中的每一项都是以下之一;该列表最多可包含一个类:
- 一个类的类名
- 一个协议的名称
- 类型别名,其基础类型是协议组合类型,协议或类。
当协议组合类型包含类型别名时,同一协议可能在定义中出现多次 —— 重复项会被忽略。例如,下面代码中 PQR
的定义等同于 P & Q & R
。
typealias PQ = P & Q
typealias PQR = PQ & Q & R
协议组合类型的语法
元类型#
元类型是指任何类型的类型,包括类,结构体,枚举值和协议。
类,结构体,枚举值类型的元类型是该类型的名称后接 .Type
。协议类型的元类型 —— 不是在运行时符合协议的具体类型 —— 是其名字后接 .Protocol
。举个例子,类 SomeClass
的元类型是 SomeClass.Type
,协议 SomeProtocol
的元类型是 SomeProtocol.Protocol
。
你可以使用后缀 self
表达式将该类型作为值来访问。举个例子,SomeClass.self
返回 SomeClass
自身,不是 SomeClass
的实例。
同样,SomeProtocol.self
返回 SomeProtocol
自身,不是运行时符合 SomeProtocol
的具体类型实例。你可以对一个类型的实例调用 type(of:)
函数来访问该实例在运行时的动态类型值。如下面例子所示:
class SomeBaseClass {
class func printClassName() {
print("SomeBaseClass")
}
}
class SomeSubClass: SomeBaseClass {
override class func printClassName() {
print("SomeSubClass")
}
}
let someInstance: SomeBaseClass = SomeSubClass()
// someInstance 在编译期的类型是 SomeBaseClass,
// someInstance 在运行时的类型是 SomeSubClass
type(of: someInstance).printClassName()
// 打印 "SomeSubClass"
有关更多信息请参见 Swift 标准库 type(of:)
。
使用初始化表达式从该类型的元类型值构造类型的实例。对于类实例,初始化过程必须使用 required
关键字标记,或者使用 final
关键字标记整个类。
class AnotherSubClass: SomeBaseClass {
let string: String
required init(string: String) {
self.string = string
}
override class func printClassName() {
print("AnotherSubClass")
}
}
let metatype: AnotherSubClass.Type = AnotherSubClass.self
let anotherInstance = metatype.init(string: "some string")
元类型的语法
类型继承语句#
类型通过继承用来指明继承了哪个类和遵循哪些协议。它以冒号(:
)开始,后面跟着类型标识符列表。
类可以从单个超类继承,并可以遵循多个协议。当定义一个类时,超类的名字必须出现在类型标识符列表的第一位,后面跟着类应该遵循的任意数量的协议。如果类没有从另一个类继承,那么标识符列表可以以协议开头。有关进一步讨论和类继承的例子,请查看继承。
其他命名型类型只继承或者遵循一个协议列表。协议类型可以继承多个其他协议。当协议继承其他协议时,协议所定义的需求集合便聚合到一起,并且任何继承了该协议的类型都必须遵循所有的协议中的定义。
在定义枚举时,类型继承语句可以是协议列表,也可以是当枚举赋值为原始值时的一个指定了原始值类型的单个的命名型类型。有关使用类型继承语句来指定其原始值类型的枚举定义的例子,请查看 原始值。
类型继承语句语法
类型继承语句 →
:
类型继承列表
类型推断#
Swift 广泛地使用了类型推断,在代码中,许多变量和表达式允许省略掉类型或者部分类型。例如,不用这样写 var x: Int = 0
,而是直接写成 var x = 0
,这样完全省略了类型 --- 编译器可以准确地推断出 x
的值是 Int
类型。与此类似,我很全部类型可以从上下文推断出来是,也可以省略一部分类型。例如,对于 let dict: Dictionary = ["A": 1]
,编译器可以推断出 dict
是 Dictionary<String, Int>
类型。
在上述两个例子中,类型信息是从表达式树形结构的叶子节点向上传递到根节点。也就是说,在 var x: Int = 0
代码中,x
的类型是通过第一次检查 0
的类型推断出来,然后再把该类型信息向上传递到根节点 (变量 x
)。
在 Swift 中,类型信息也可以向相反的方向传递,即从根节点向下传递到叶子节点。比如下面的例子,在常量 eFloat
声明语句中,显式的类型注解 (: Float
)可以推断出数值字面量 2.71828
是 Float
类型,而不是 Double
。
let e = 2.71828 // e 的类型被推断为 Double.
let eFloat: Float = 2.71828 // eFloat 的类型是 Float 类型.
Swift 中的类型推断是在单个表达式或者语句上执行的。这就意味着,对表达式或其中的子表达式做类型检查时,用来推断一个省略了全部类型或者部分类型的表达式的类型所需要的信息都必须是可访问的。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。