对于 Objective-C 的开发者,元组 类型是个有些陌生的概念。开发者可能会把元组和结构或类产生混淆。但事实上,元组比前面两者更加基础。我喜欢把元组看做一个微型结构体或 精简版结构体,该说法只用在极小的领域内,并随后被丢弃。
根据 Apple 关于 Swift Types 的官方文档:
元组类型是用括号包围,由一个逗号分隔的零个或多个类型的列表。
它是由其它类型组合而成的类型。元组可能包含零或多个类型,比如 字符串、整数、字符、布尔以及其它元组。同时请注意,元组是 *值传递,而不是引用。
1 |
let foo = (0, false, “Hello”) |
上面是一个包含三个独立元素的元组,如果需要用较冗长的方式声明,也可以把上面的元组定义为:
1 |
let foo: (Int, Bool, String) = (0, false, “Hello”) |
如果我们想访问来自这些来自元组的单独元素,我们可能不得不使用类似数组的索引来访问:
1 2 3 |
print(foo.0) // print: “0” print(foo.1) // print: “false” print(foo.2) // print: “Hello” |
但愿/现在你能很好理解该元组的语法。下面将以混合方式创建包含其他元组的新元组。初期样式:
1 2 3 4 |
let bar: (Int, (Bool, String)) = (1, (false, “Hello”)) print(bar.0) // print: “0” print(bar.1.0) // print: “false” print(bar.1.1) // print: “Hello” |
这绝对疯狂,该点语法起初开起来像申请某物的某种语法版本。其原因是其在索引号为 1 的值类型是另一个元组,因此它使用了自己的存取器。
但是,为了避免使用索引,这必须记住与索引对应的值/,最好的做法是尽可能地给你的元组属性命名。
1 2 3 4 |
let person = (age: 28, isTall: false, name: “Andyy”) print(person.age) // print: “28” print(person.isTall) // print: “false” print(person.name) // print: “Andyy” |
何处该用元组
当你想让一个函数能够返回多种类型时,这是元组的最佳使用场景。/如果你是 Objective-C 开发者,这个概念听起来有些奇怪,甚至是不可能的。我们之前为了解决这种问题,我们通常不得不返回一个 NSArray 或 NSDictionary。尽管如此,这种解决方案是危险的。因为一旦我们把 NSArray 转换成错误类型的对象,并调用指定方法会发生什么?这会导致在应用在运行时的闪退。
因此,让我们看看我们如何写出一个返回多种/类型的函数,而不是为了应对 Objective-C 的局限性而想出一个麻烦的方案。
1 2 3 4 5 6 7 8 9 |
typealias Person = (age: Int, isTall: Bool, name: String) func getMultipleValues() -> Person { let person: Person = (age: 28, isTall: false, name: “Andyy”) return person } let andyy = getMultipleValues() print(andyy.age) // print: "28" print(andyy.isTall) // print: "false" print(andyy.name) // print: "Andyy" |
为了提高代码可读性,我使用 typealias 预定义构造元组,也许你也该这样写,这样防止了在代码中意外地错误修改它的结构。
但是,正如你在上面看到的,这里有个叫做 getMultipleValues() 函数,它返回了一个包含三个独立的和不同数据类型的元组,每一个都有自己对应的变量名。
结构实例(不建议使用)
更新:Swift 3.0 将会废弃 建议:SE-0029 从函数应用里移除元组隐式转换
在文章的开头,我戏称元组为 精简版结构体。一个鲜为人知的事实是,元组可以用来实例化结构体。
1 |
let size = CGSize(width: 100, height: 100) |
结构体 CGSize 有两个元素,其中任何一个都可以是 CGFloat、Double 或 Int。 但是,如果我们传递一个元组进入这个函数会发生什么?
1 2 |
let arguments: (width: CGFloat, height: CGFloat) = (100, 100) var size = CGSize(arguments) |
是不是很酷?但是有一点需要注意,元组的值必须和结构的构造函数的参数完全匹配,更改顺序、参数名称、数量,都会导致阻止Xcode继续编译。
1 2 |
let arguments: (height: CGFloat, width: CGFloat) = (100, 100) !! var size = CGSize(arguments) |
“不能通过‘(height: CGFloat, width:CGFloat)’参数列表调用 ‘CGSize’ 类型的初始化”——Xcode
你看我做了什么?我通过交换高度和宽度的位置,改变了参数的布局。尽管它们都叫做 CGFloats ,编译器不能推断出你想要做什么。
有趣的事实
如果曾使用过 Swift,你很可能在不知道的情况下已经使用元组作为返回类型了。下面,我们有三个单独的函数,它们都做了相同的事情(就是不做任何事),并且没有返回任何值。
1 2 3 |
func doNothingA() -> Void { } func doNothingB() -> () { } func doNothingC() { } |
doNothingA() 用了更前卫的方式定义了一个空函数;而 doNothingB() 用了原始方式声明了空函数;doNothingC() 则使用缺省方式声明空函数明,省略了返回值。
最有趣的是,“Void”是“()”的 typealias,也就是空元组。如果你记得这篇文章的开头,你就会想到我提到过元组可以包含 零 或多个类型。
选择你的发音
人们如何发出单词 “tuple” 的音有很大的争议。 有些人喜欢说 “Two-pull”,而其他人说:“Tup-pl”,而最疯狂的人说:“Choo-pl”。 无论您喜欢哪一种,准备把它说出来吧。
本文的 示例代码 可以在 GitHub 上找到。这只是一个简单的 Playgrounds 文件,来帮助你加强对元组理解。