为什么要"嵌套"
在 Swift 中,我们经常会写一些"小工具"类型:
- 只在某个类/结构体里用到的枚举
- 仅服务于一条业务逻辑的辅助结构体
- 与外部世界无关的私有协议
如果把它们全部写成顶层类型,会导致:
- 命名空间污染(Top-Level 名字过多)
- 可读性下降("这个类型到底给谁用?")
- 访问控制粒度变粗(想私有却不得不 public)
嵌套类型(Nested Types)正是为了解决这三个痛点:把"辅助类型"放进"主类型"内部,让代码的"作用域"与"视觉层次"保持一致。
语法一览:如何"套娃"
swift
// 外层:主类型
struct BlackjackCard {
// 嵌套枚举 ①
enum Suit: Character {
case spades = "♠"
case hearts = "♥"
case diamonds = "♦"
case clubs = "♣"
}
// 嵌套枚举 ②
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace
// 在枚举里再嵌套一个结构体 ③
struct Values {
let first: Int
let second: Int? // Ace 才有第二值
}
// 计算属性,返回嵌套结构体
var values: Values {
switch self {
case .ace:
return Values(first: 1, second: 11)
case .jack, .queen, .king:
return Values(first: 10, second: nil)
default: // 2...10
return Values(first: self.rawValue, second: nil)
}
}
}
// 主类型自己的属性
let rank: Rank
let suit: Suit
// 计算属性,拼接描述
var description: String {
let valueDesc = rank.values.second == nil ?
"\(rank.values.first)" :
"\(rank.values.first) 或 \(rank.values.second!)"
return "\(suit.rawValue)\(rank.rawValue)(点数 \(valueDesc))"
}
}
知识点逐条拆解
-
嵌套深度不限
上面
Values结构体嵌套在Rank枚举里,Rank又嵌套在BlackjackCard中,形成三级嵌套。只要你愿意,可以继续往下套。 -
名字自动带上"前缀"
在外部使用时,编译器强制你加"外层名字."前缀,天然起到命名空间隔离:
swift
let color = BlackjackCard.Suit.hearts // 不会和 Poker.Suit.hearts 冲突
-
访问控制可逐层细化
如果
BlackjackCard是public,而Values声明为private,那么模块外部无法感知Values存在,实现细节被彻底隐藏。 -
成员构造器依旧生效
因为
BlackjackCard是结构体且未自定义init,编译器仍会生成逐成员构造器:
swift
let card = BlackjackCard(rank: .ace, suit: .spades)
print(card.description) // ♠ace(点数 1 或 11)
注意:.ace、.spades 可以省略前缀,因为 Swift 能根据形参类型推断出 Rank 与 Suit。
再举三个日常开发场景
- UITableView 嵌套数据源
swift
class SettingsViewController: UITableViewController {
// 仅在本控制器里使用的模型
private enum Section: Int, CaseIterable {
case account, privacy, about
var title: String {
switch self {
case .account: return "账号"
case .privacy: return "隐私"
case .about: return "关于"
}
}
}
private typealias Row = (icon: UIImage?, text: String, action: () -> Void)
private var data: [Section: [Row]] = [:]
}
- Network 嵌套错误
swift
struct API {
enum Error: Swift.Error {
case invalidURL
case httpStatus(code: Int)
case decodeFailure(underlying: Swift.Error)
}
func request() async throws -> Model {
guard let url = URL(string: "https://example.com") else {
throw Error.invalidURL
}
...
}
}
- SwiftUI 嵌套模型
swift
struct EmojiMemoryGame: View {
// 仅在本 View 文件里使用
private struct Card: Identifiable {
let id = UUID()
let emoji: String
var isFaceUp = false
}
@State private var cards: [Card] = []
}
总结与最佳实践
-
命名空间 > 前缀
与其写
BlackjackSuit、BlackjackRank,不如直接嵌套,用BlackjackCard.Suit既简洁又清晰。 -
能 private 就 private
把嵌套类型默认写成
private,直到外部真的需要再放宽权限,避免"泄露实现"。 -
不要"为了嵌套而嵌套"
如果某个类型在多个业务模块出现,继续嵌套反而会增加引用成本,此时应提升为顶层
internal或public。 -
与
typealias搭配食用更佳当嵌套路径过长时,可在当前文件顶部
typealias CardSuit = BlackjackCard.Suit,既保留命名空间,又减少手指负担。 -
在 Swift Package 中作为"实现细节"
公开接口只暴露最外层
public struct BlackjackCard,所有辅助枚举/结构体保持internal或private,后续迭代可随意重构而不破坏 SemVer。