2.21 嵌套类型

原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/nestedtypes/

2.21 嵌套类型

在另一个类型的作用域内定义类型。

枚举常常是为支持某个特定类或结构体的功能而创建的。类似地,把工具性结构体纯粹定义在某个更复杂类型的上下文中使用,或者定义通常与某个特定类型搭配使用的协议,也都很方便。为此,Swift 允许你定义嵌套类型,把枚举、结构体和协议这类辅助类型嵌套在它们所支持的那个类型的定义之中。

要把一个类型嵌套在另一个类型中,就把它的定义写在它所要支持的类型的外层花括号之内。类型可以按需要嵌套任意多层。

嵌套类型的实际运用

下面的例子定义了一个名为 BlackjackCard 的结构体,用来为二十一点游戏中使用的扑克牌建模。BlackjackCard 结构体包含两个嵌套枚举类型:Suit 和 Rank。

在二十一点中,A 的点数既可以是 1,也可以是 11。这一特性由一个名为 Values 的结构体表示,它嵌套在 Rank 枚举之中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct BlackjackCard {
    // 嵌套的 Suit 枚举
    enum Suit: Character {
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }

    // 嵌套的 Rank 枚举
    enum Rank: Int {
        case two = 2, three, four, five, six, seven, eight, nine, ten
        case jack, queen, king, ace
        struct Values {
            let first: Int, second: Int?
        }
        var values: Values {
            switch self {
            case .ace:
                return Values(first: 1, second: 11)
            case .jack, .queen, .king:
                return Values(first: 10, second: nil)
            default:
                return Values(first: self.rawValue, second: nil)
            }
        }
    }

    // BlackjackCard 的属性和方法
    let rank: Rank, suit: Suit
    var description: String {
        var output = "suit is \(suit.rawValue),"
        output += " value is \(rank.values.first)"
        if let second = rank.values.second {
            output += " or \(second)"
        }
        return output
    }
}

Suit 枚举描述四种常见的扑克花色,并带有一个表示其符号的 Character 原始值。

Rank 枚举描述十三种可能的扑克点数,并带有一个表示其面值的 Int 原始值。(这个 Int 原始值不用于 J、Q、K 和 A。)

如上所述,Rank 枚举还定义了自己进一步嵌套的结构体 Values。这个结构体封装了"大多数牌只有一个点数、但 A 有两个点数"这一事实。Values 结构体用两个属性来表示:

  • first,类型为 Int
  • second,类型为 Int?,也就是"可选 Int"

Rank 还定义了一个计算属性 values,返回一个 Values 结构体实例。这个计算属性考察牌的点数,并根据点数用合适的值初始化新的 Values 实例:对 jack、queen、king 和 ace 使用特殊值,对数字牌则使用该点数的 Int 原始值。

BlackjackCard 结构体本身有两个属性——rank 和 suit。它还定义了一个名为 description 的计算属性,用 rank 和 suit 中存放的值构造出这张牌名称与点数的描述。description 属性用可选绑定检查是否有第二个值需要显示,如果有,就为这个第二值插入额外的描述细节。

由于 BlackjackCard 是一个没有自定义构造器的结构体,它拥有一个隐式的逐成员构造器,详见结构体类型的逐成员构造器。你可以用这个构造器初始化一个新常量 theAceOfSpades:

1
2
3
let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print("theAceOfSpades: \(theAceOfSpades.description)")
// 输出 "theAceOfSpades: suit is ♠, value is 1 or 11"。

尽管 Rank 和 Suit 嵌套在 BlackjackCard 之中,它们的类型仍可以从上下文推断出来,因此这个实例的初始化可以直接用成员名(.ace 和 .spades)引用枚举成员。在上面的例子里,description 属性正确地报告黑桃 A 的点数是 1 或 11。

引用嵌套类型

要在定义上下文之外使用嵌套类型,就在它的名字前加上它所在类型的名字:

1
2
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// heartsSymbol 是 "♡"

对于上面的例子来说,这使得 Suit、Rank 和 Values 的名字可以故意起得很短,因为这些名字天然地被它们定义所处的上下文限定了。