本节摘要:零散的变量凑不成一棵树,数据需要细胞级的组织。本节造两件值类型容器:枚举 PlantKind 把"品种"收进封闭集合(含原始值与关联值两种形态),结构体 Plant 把一株植物的全部属性打包(成员初始化器、可变方法、计算属性)。核心直觉只有一句:值类型赋值即拷贝,拷贝即隔离——这是 Swift 把 UI 数据模型默认做成 struct 的原因。
前三节的养分都是散装的。真到界面那一步,列表的每一行、详情页的整张卡片,都需要"一坨完整的数据"作为一个单位被传递。Swift 提供两种细胞:枚举管"有限状态",结构体管"一组属性"。先看枚举——它比多数语言里的枚举强大得多,能带原始值,甚至能带关联值:
// 形态一:原始值枚举——每个成员带一个固定标牌 enum PlantKind: String { case foliage = "观叶" // 龟背竹这类 case succulent = "多肉" // 耐旱储水型 case fern = "蕨类" // 喜湿阴生型 } let kind = PlantKind.fern print(kind.rawValue) // 蕨类 print(PlantKind(rawValue: "多肉") ?? .foliage) // 反查:多肉(查不到返回 nil,2.7 讲) // 形态二:关联值枚举——每个成员带自己的"行李" enum CareEvent { case watered(amountMl: Int) // 浇水事件带水量 case repotted(toPotSize: Int) // 换盆事件带新盆尺寸 case fertilized(brand: String, days: Int) } let today: [CareEvent] = [.watered(amountMl: 200), .fertilized(brand: "缓释肥", days: 90)] for event in today { switch event { case .watered(let ml): print("浇水 \(ml) 毫升") case .repotted(let size): print("换到 \(size) 号盆") case .fertilized(let b, let d): print("施 \(b),肥效 \(d) 天") } } // 输出: // 浇水 200 毫升 // 施 缓释肥,肥效 90 天
关联值是"带数据的分支",它把"事件种类 + 事件参数"装进一个不可分割的值,界面上第 4 章的记录列表就靠它区分行样式。
结构体是属性的组合体。给阳台花园造主角 Plant:
struct Plant { var name: String var kind: PlantKind var intervalDays: Int var passedDays: Int = 0 // 属性给默认值,初始化器就能少传 // 计算属性:不存值,每次现算(界面上"倒计时天数"就是这么来的) var remainingDays: Int { max(intervalDays - passedDays, 0) } var isThirsty: Bool { passedDays >= intervalDays } // 可变方法:修改自身属性必须标 mutating(值类型改自己=造个新的换掉自己) mutating func water() { passedDays = 0 } mutating func skipADay() { passedDays += 1 } } // 成员初始化器免费赠送:按属性名传参即可 var monstera = Plant(name: "龟背竹", kind: .foliage, intervalDays: 3) monstera.skipADay(); monstera.skipADay() print("\(monstera.name) 还剩 \(monstera.remainingDays) 天") // 龟背竹 还剩 1 天 print(monstera.isThirsty ? "该浇水了" : "不用浇") // 不用浇 monstera.water() print("浇水后剩余 \(monstera.remainingDays) 天") // 浇水后剩余 3 天

// 图中行为的代码验证 var backup = monstera // 此刻分裂出 backup backup.skipADay() // 只动 backup print(monstera.passedDays) // 0(water 后的值),原体未被波及 print(backup.passedDays) // 1 print(monstera === backup) // 编译错误:=== 只用于类,结构体根本没有"同一性"概念
背景:详情页需要"植物 + 最近一次养护事件"两坨数据,散着传要写一长串参数。操作:用嵌套结构体打包,枚举做事件载体。
struct PlantDetail { let plant: Plant let latestEvent: CareEvent var subtitle: String { // 计算属性现算副标题 switch latestEvent { case .watered(let ml): return "上次浇水 \(ml) 毫升" case .repotted(let s): return "现居 \(s) 号盆" case .fertilized(let b, _): return "最近施肥:\(b)" } } } let detail = PlantDetail(plant: monstera, latestEvent: .watered(amountMl: 200)) print(detail.subtitle) // 上次浇水 200 毫升 print(detail.plant.remainingDays) // 3
结果:详情页拿一个值就能渲染整页。解读:注意 subtitle 是计算属性——数据没多存一份,展示逻辑却集中在模型里,界面层零逻辑。变式:事件历史不止一条时把 latestEvent 换成数组,subtitle 取首个;到第 5 章这个结构体加一个 ObservableObject 包装(2.5 的类)就能驱动 SwiftUI 刷新——值类型做数据、引用类型做状态源,分工在那时定型。
细胞会分裂,但有些东西必须共享——下一节讲类:一根大家共用的树干。