Swift: Swift 类与结构体:值类型与引用类型的选择指南

类和结构体是 Swift 中构建数据模型的两大核心类型。它们有很多相似之处,但在内存管理和传值方式上有本质区别。本课帮你理清两者的选择策略。

1. 你将学到


2. 一个 iOS 开发者的真实故事

(1) 痛点:用户账户改了 A 处,B 处也变了

Alice 在建模用户账户时选择了 class:

SWIFT
class UserAccount {
    var name: String
    var balance: Double
    init(name: String, balance: Double) {
        self.name = name
        self.balance = balance
    }
}
let account1 = UserAccount(name: "Alice", balance: 1000)
let account2 = account1  // 引用同一个实例
account2.name = "Bob"
print(account1.name)  // "Bob" —— account1 也被改了!

Alice 只是想创建一个新用户,结果意外修改了原数据。引用类型的副作用在不知情的情况下传播到了整个代码库。

(2) 结构体作为值类型的解法

SWIFT
struct UserAccount {
    var name: String
    var balance: Double
}
var account1 = UserAccount(name: "Alice", balance: 1000)
var account2 = account1  // 复制一份独立的数据
account2.name = "Bob"
print(account1.name)  // "Alice" —— account1 不受影响

结构体赋值时进行值拷贝,每个实例互不影响。

(3) 收益:可预测的数据行为

维度 class(引用类型) struct(值类型)
赋值行为 共享同一实例 独立拷贝
意外修改风险 高(别名效应)
内存分配 堆分配 栈分配(更高效)
线程安全 需手动同步 天然隔离
适用场景 需共享状态 数据模型、值语义

3. 定义类与结构体

(1) 基本语法对比

100%
graph TB
    subgraph class
        A["class Person {"] 
        B["    var name: String"]
        C["    var age: Int"]
        D["    func greet() { }"]
        E["}"]
    end
    subgraph struct
        F["struct Person {"]
        G["    var name: String"]
        H["    var age: Int"]
        I["    func greet() { }"]
        J["}"]
    end
功能 class struct 说明
属性 存储属性和计算属性
方法 实例方法和类型方法
初始化器 class 不自动生成成员初始化器
继承 struct 不能继承
析构器 struct 没有 deinit
引用计数 struct 不需要 ARC
类型转换 as?/as! 仅用于 class

▶ 示例:类与结构体的定义

SWIFT
// ============================================
// 类和结构体的定义对比
// ============================================
// 类定义——需要手动写 init
class PersonClass {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    func introduce() {
        print("Hi, I'm \(name), \(age) years old")
    }
}
// 结构体定义——自动获得成员初始化器
struct PersonStruct {
    var name: String
    var age: Int
    func introduce() {
        print("Hi, I'm \(name), \(age) years old")
    }
}
let p1 = PersonClass(name: "Alice", age: 28)
p1.introduce()
let p2 = PersonStruct(name: "Bob", age: 22)  // 自动生成 init
p2.introduce()

输出:

TEXT 📖 仅展示
Hi, I'm Alice, 28 years old
Hi, I'm Bob, 22 years old

4. 值类型 vs 引用类型

(1) 核心区别

100%
graph LR
    subgraph "值类型(struct)"
        A["var a = Point(x: 1, y: 2)"]
        B["var b = a"]
        C["b.x = 5"]
        D["a.x → 1(独立)"]
        A --> B --> C --> D
    end
    subgraph "引用类型(class)"
        E["var a = Point(x: 1, y: 2)"]
        F["var b = a"]
        G["b.x = 5"]
        H["a.x → 5(共享)"]
        E --> F --> G --> H
    end
维度 值类型(struct) 引用类型(class)
赋值语义 拷贝内容 拷贝引用
相等判断 == 比较值 === 比较身份
存储位置 栈(更快) 堆(需 ARC 管理)
let 常量 属性不可变 引用不可变但属性可变
函数传参 默认拷贝 默认共享引用

▶ 示例:赋值行为对比

SWIFT
// ============================================
// 值类型与引用类型的赋值行为
// ============================================
// 结构体——值类型
struct Position {
    var x: Int
    var y: Int
}
var posA = Position(x: 10, y: 20)
var posB = posA
posB.x = 99
print("posA: (\(posA.x), \(posA.y))")  // 不变
print("posB: (\(posB.x), \(posB.y))")  // 只影响 posB
// 类——引用类型
class Location {
    var x: Int
    var y: Int
    init(x: Int, y: Int) { self.x = x; self.y = y }
}
var locA = Location(x: 10, y: 20)
var locB = locA
locB.x = 99
print("locA: (\(locA.x), \(locA.y))")  // 也跟着变了!
print("locB: (\(locB.x), \(locB.y))")

输出:

TEXT 📖 仅展示
posA: (10, 20)
posB: (99, 20)
locA: (99, 20)
locB: (99, 20)

(2) 恒等运算符 ===

引用类型可以使用 === 判断两个变量是否指向同一个实例:

SWIFT
let loc1 = Location(x: 5, y: 10)
let loc2 = loc1
let loc3 = Location(x: 5, y: 10)  // 不同实例
print(loc1 === loc2)  // true — 同一实例
print(loc1 === loc3)  // false — 不同实例
💡 提示: === 是 identity operator(恒等运算符),不同于 ==(相等运算符)。值类型只能使用 ==(需实现 Equatable),不能使用 ===


5. mutating 方法与选择策略

(1) mutating 关键字

结构体的实例方法默认不能修改属性。如果需要修改,必须标记为 mutating

方法类型 class struct(非 mutating) struct(mutating)
修改属性 ✅ 允许 ❌ 编译错误 ✅ 允许
修改 self ✅ 允许 ❌ 不允许 ✅ 允许
调用前提 无限制 let 常量也可调用 变量(var)才能调用

▶ 示例:mutating 方法

SWIFT
// ============================================
// struct 的 mutating 方法
// ============================================
struct Counter {
    var count = 0
    // 非 mutating——只读方法
    func display() {
        print("Count: \(count)")
    }
    // mutating——修改属性
    mutating func increment() {
        count += 1
    }
    // mutating——替换整个实例
    mutating func reset() {
        self = Counter()  // 替换 self
    }
}
var counter = Counter()  // 必须是 var 才能调用 mutating
counter.increment()
counter.increment()
counter.display()   // Count: 2
counter.reset()
counter.display()   // Count: 0

输出:

TEXT 📖 仅展示
Count: 2
Count: 0

(2) 选择策略

场景 推荐类型 原因
数据模型(Point、Size、User) struct 值语义,安全可预测
需要继承(UIView 子类、BaseViewController) class 继承是 class 独有的
需共享可变状态 class 引用类型自动共享
需要 ObjC 互操作 class @objc 需要 class
轻量封装(包装少量值) struct 栈分配,零开销
需要析构(deinit)清理资源 class struct 没有 deinit

▶ 示例:结构体作为模型的首选

SWIFT
// ============================================
// 为什么现代 Swift 偏向用 struct 做模型
// ============================================
struct User {
    let id: Int
    var name: String
    var email: String
    var score: Int
    mutating func addScore(_ points: Int) {
        score += points
    }
}
var user = User(id: 1, name: "Alice", email: "alice@example.com", score: 100)
user.addScore(50)
// 拷贝修改模式——安全地创建变化版本
let admin = User(id: 2, name: "Admin", email: "admin@example.com", score: 0)
// admin.addScore(10)  // 编译错误!let 常量不能调用 mutating
var editor = admin
editor.name = "Editor"  // 独立修改,不影响 admin

输出:

TEXT 📖 仅展示
(无控制台输出,演示值语义——editor.name = "Editor" 不影响 admin.name)

6. 完整示例:银行账户系统

SWIFT
// ============================================
// 完整示例:银行账户系统
// 功能:class 做账户管理,struct 做交易记录
// ============================================
import Foundation
// 1. 交易记录——值类型结构体
struct Transaction {
    let id: Int
    let amount: Double
    let type: String  // "deposit" 或 "withdraw"
    let date: Date
    func description() -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return "[\(formatter.string(from: date))] \(type): $\(amount)"
    }
}
// 2. 账户——引用类型类(共享状态)
class BankAccount {
    let accountNumber: String
    var balance: Double
    private var transactions: [Transaction] = []
    private var nextId = 1
    init(accountNumber: String, initialBalance: Double = 0) {
        self.accountNumber = accountNumber
        self.balance = initialBalance
    }
    func deposit(amount: Double) {
        guard amount > 0 else { return }
        balance += amount
        let tx = Transaction(id: nextId, amount: amount, type: "deposit", date: Date())
        transactions.append(tx)
        nextId += 1
    }
    func withdraw(amount: Double) -> Bool {
        guard amount > 0 && amount <= balance else { return false }
        balance -= amount
        let tx = Transaction(id: nextId, amount: amount, type: "withdraw", date: Date())
        transactions.append(tx)
        nextId += 1
        return true
    }
    func printStatement() {
        print("=== Account \(accountNumber) ===")
        print("Balance: $\(balance)")
        print("Transactions:")
        for tx in transactions {
            print("  \(tx.description())")
        }
    }
}
// 3. 使用
let account = BankAccount(accountNumber: "ACC-001", initialBalance: 1000)
account.deposit(amount: 500)
account.withdraw(amount: 200)
account.deposit(amount: 100)
account.printStatement()

输出:

TEXT 📖 仅展示
=== Account ACC-001 ===
Balance: $1400.0
Transactions:
  [2026-07-30] deposit: $500.0
  [2026-07-30] withdraw: $200.0
  [2026-07-30] deposit: $100.0

❓ 常见问题

Q 为什么 Apple 推荐优先用 struct?
A 值类型更安全、更可预测。没有引用共享的副作用,也没有循环引用和引用计数的开销。Swift 标准库中的 String、Array、Dictionary 都是 struct。
Q struct 有性能优势吗?
A 是的。struct 分配在栈上,不需要引用计数管理,没有堆分配的开销。但大型 struct 在函数间传递时的拷贝成本可能高于引用类型的传引用成本。Swift 的 Copy-on-Write 机制优化了这个问题。
Q class 的 init 必须手动写吗?
A 对于 class,如果所有属性都有默认值,且没有定义任何初始化器,Swift 会生成一个默认无参 init。但不会像 struct 那样自动生成逐一成员初始化器(memberwise init)。
Q struct 可以有懒加载属性吗?
A 可以。lazy var 在 struct 中也能工作,但因为 struct 不可变时不能访问 lazy 属性,实际使用限制较多。在 class 中使用 lazy 更自然。
Q mutating 方法在 class 中能用吗?
A 不能。mutating 只能用于值类型(struct 和 enum)。class 的方法默认就可以修改属性,不需要 mutating 关键字。

📖 小节


📝 作业

  1. 基础题: 定义一个 Book 结构体,包含 titleauthorpages 属性和一个 summary() 方法。创建两个实例并赋值测试值类型拷贝特性。
  2. 进阶题: 定义一个 TemperatureSensor 类,包含 currentTemp 属性和 readings: [Double] 历史记录数组。实现 recordTemp(_:) 方法和 average() 方法。创建实例,记录 5 个温度并打印平均值。
  3. 挑战题: 设计一个「团队协作系统」:用 Team 类管理共享成员列表,用 Member 结构体表示个人资料。Team 提供 addMember(_:)listMembers() 方法。Membermutating 方法来更新角色。验证 class 的共享引用特性。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏