Swift 类与结构体
类(class)和结构体(struct)是 Swift 自定义类型的两大主力。它们语法相似,但本质不同——一个是引用类型,一个是值类型。Apple 官方建议"默认用 struct",但 class 仍不可替代。本章讲透它们。
1. struct:值类型,首选
struct 是 Swift 推崇的类型形式。标准库的 Int、String、Array、Dictionary 全是 struct。先看一个完整例子:
// struct:值类型,Swift 默认推荐
struct Point {
var x: Double
var y: Double
// 自动有"成员初始化器"(class 没有)
// 用 Point(x: 1, y: 2) 创建
// 计算属性
var distance: Double {
return (x * x + y * y).squareRoot()
}
// 方法
func translated(by dx: Double, dy: Double) -> Point {
return Point(x: x + dx, y: y + dy)
}
// 修改 self 的方法必须加 mutating
mutating func translate(by dx: Double, dy: Double) {
x += dx
y += dy
}
}
var p = Point(x: 3, y: 4)
print(p.distance) // 5.0
let p2 = p.translated(by: 1, dy: 1)
print(p2) // Point(x: 4.0, y: 5.0)
p.translate(by: 10, dy: 10)
print(p) // Point(x: 13.0, y: 14.0)几个要点:
- 自动成员初始化器:struct 自动生成
Point(x: 1, y: 2)这种 init,class 没有这待遇。 - 计算属性:不存值,每次访问都算。
- mutating:struct 方法默认不能改 self,要改加 mutating。
2. 值类型 vs 引用类型(核心区别)
这是 Swift 最关键的设计决策,直接影响代码行为:
// 值类型:赋值即复制
struct Size {
var width: Double
var height: Double
}
var a = Size(width: 100, height: 50)
var b = a // 复制!
b.width = 200
print(a.width) // 100(a 不受影响)
print(b.width) // 200
// 对比引用类型
class Box {
var value = 0
}
let x = Box()
let y = x // 复制的是引用!
y.value = 99
print(x.value) // 99(x 也变了!)- struct(值类型):赋值/传参时复制。改 b 不影响 a。线程安全,心智负担低。
- class(引用类型):赋值/传参时复制引用。改 y 就是改 x,因为它们指向同一对象。
这条差异决定了何时用谁——下面会总结。
3. class:引用类型,支持继承
// class:引用类型,支持继承
class Animal {
var name: String
// 显式初始化器(class 没有自动 init)
init(name: String) {
self.name = name
}
func speak() -> String {
return "\(name) 发出声音"
}
// 析构器(类似 dealloc)
deinit {
print("\(name) 被销毁")
}
}
// 继承
class Dog: Animal {
var breed: String
init(name: String, breed: String) {
self.breed = breed
super.init(name: name) // 必须先初始化本类属性,再调 super.init
}
// 重写父类方法
override func speak() -> String {
return "\(name) ((breed)) 汪汪叫!"
}
}
let d = Dog(name: "旺财", breed: "柴犬")
print(d.speak()) // 旺财 (柴犬) 汪汪叫!
// 多态
let animals: [Animal] = [Animal(name: "x"), Dog(name: "y", breed: "z")]
for a in animals {
print(a.speak()) // 各自调自己版本
}class 的特点:
- 需要显式 init(没有自动成员初始化器)。
- 支持继承:子类获得父类的属性和方法,可以
override。 - 有多态:父类引用指向子类对象,自动调子类方法。
- 有 deinit:对象销毁前调用,适合清理资源。
- 引用计数(ARC):Swift 自动管理,无需手动 retain/release。
4. 属性的种类
struct 和 class 都可以有四种属性:
struct User {
var name: String
// 存储属性(默认值)
var score: Int = 0
// 计算属性:每次访问都计算
var isVip: Bool {
return score > 100
}
// getter + setter
var scoreInHundreds: Int {
get { return score / 100 }
set { score = newValue * 100 } // newValue 是新值
}
// 类型属性(类似 static)
static let maxScore = 999
// 属性观察者:willSet / didSet
var level: Int = 0 {
willSet { print("即将变为 \(newValue)") }
didSet { print("从 \(oldValue) 变为 \(level)") }
}
}
var u = User(name: "小明", score: 50, level: 1)
u.level = 2 // 触发 willSet/didSet
u.scoreInHundreds = 5
print(u.score) // 500- 存储属性:存值,可设默认值。
- 计算属性:get(可能还有 set),不直接存值。
- 类型属性:用
static,属于类型本身而非实例。 - 属性观察者:willSet / didSet,在属性变化前后触发。极适合 UI 联动。
5. Equatable / Hashable / Comparable
Swift 让自定义类型支持判等、哈希、比较,只需声明协议:
// 让 struct 自动支持判等
struct Point2: Equatable, Hashable {
var x: Double
var y: Double
// Equatable 和 Hashable 默认实现会比较所有字段
}
let p1 = Point2(x: 1, y: 2)
let p2 = Point2(x: 1, y: 2)
print(p1 == p2) // true
print(p1.hashValue) // 整数,可作 Set/Dict 的 key
// Comparable:自定义排序
struct Score: Comparable {
let value: Int
static func < (lhs: Score, rhs: Score) -> Bool {
return lhs.value < rhs.value
}
// 还需实现 == (来自 Equatable)
}
let s = [Score(value: 3), Score(value: 1), Score(value: 2)]
print(s.sorted()) // [1, 2, 3]Swift 会自动合成 Equatable/Hashable 的实现(比较所有存储属性)。这让自定义类型可以直接用 ==、作 Set 元素、作字典 key——非常方便。
6. 何时用 struct,何时用 class?
Apple 官方建议,符合以下任一条件用 class,否则用 struct:
- 需要继承(子类共享父类行为)。
- 需要引用语义(多处共享同一实例,改一处全见)。
- 需要deinit清理资源(如关闭文件、释放句柄)。
- 使用 Objective-C 互操作(NSObject 子类)。
其他场合——模型、配置、值对象、几何——都用 struct。Swift 社区 90% 的类型是 struct。
7. 引用计数(ARC)简述
class 实例由 ARC 自动管理引用计数,大多数场合无需操心。但要注意循环引用——两个 class 互相强引用会造成内存泄漏。解决方案:用 weak 或 unowned 打破环:
class Person { var dog: Dog? }
class Dog { weak var owner: Person? } // weak 打破环
let p = Person()
let d = Dog()
p.dog = d
d.owner = p
// 不会循环引用,d.owner 是 weakstruct 没有这个问题,因为值类型不涉及引用计数——这是 struct 安全的另一个原因。
8. 常见陷阱
- struct 里改 self:忘加
mutating,编译错误。 - let struct:声明成 let 的 struct 不能调 mutating 方法(因为不能改)。
- 继承的 init:子类 init 必须先初始化自己的属性,再调 super.init。
- 循环引用:class 互相引用要 weak/unowned。
小结
struct 是 Swift 首选(值类型,简单安全),class 用于需要继承或引用语义的场景。掌握它们的属性种类、init 规则、Equatable/Hashable 协议,你就能写出整洁的类型系统。下一篇讲 Swift 抽象的另一面——协议。
← 上一篇 Swift 字典
下一篇 Swift 协议 →