Swift 里的 struct、class、actor 我是怎么选的

41 4.9~6.3 分钟

刚开始写 Swift 的时候,我老老实实什么都用 class。因为以前写过一点 OC,脑子里就只有“对象=class”。

结果没多久就踩坑了:我写了个 User 模型,在列表和详情页同时用,结果改了详情页的用户名,列表里也跟着变了。我还以为是 UI bug,debug 半天才发现:哦,原来 class引用语义,两个页面其实拿的是同一个对象。那一刻我才开始意识到,Swift 推荐 struct 不是闹着玩的。

struct 的一个小例子

struct User {
  var name: String
  var age: Int
}

var u1 = User(name: "Tom", age: 20)
var u2 = u1   // 拷贝一份新数据
u2.name = "Jerry"

print(u1.name) // Tom
print(u2.name) // Jerry

这里就很直观:struct 是 值类型,改了 u2 不会影响 u1。就像你复印了一张表格,在副本上写乱七八糟,原件一点没动。

class:有身份的对象

后来我慢慢接受了 Swift 的“值语义优先”思想,struct 用得越来越多。

但是有些场景,用 struct 就很别扭。比如 UI 控件。

想象一下,如果 UIButton 是 struct,每次你传来传去它都复制一份,那你点按钮的时候根本不知道点到的是哪份副本😂。这时候 class 的 引用语义就很合适:全世界只有这一份对象,谁改了属性,大家看到的都是同一份状态。

小例子:

class Dog {
  var name: String
  init(name: String) { self.name = name }
}

let d1 = Dog(name: "旺财")
let d2 = d1   // 不是拷贝,而是两个引用指向同一个对象
d2.name = "小黑"

print(d1.name) // 小黑

你看,这种“同一条狗换了个名字”的感觉,很贴合 class。

actor:并发里的“管家”

直到 Swift 5.5 出了 Concurrency,我才知道还有个新东西:actor

说实话,我第一次用的时候很懵:写了个计数器,结果编译器一直让我加 await。我当时想:“这啥啊,我就加个数还得异步?”

后来才理解,actor 的设计就是为了线程安全。它相当于给这个对象配了个“管家”,所有人想访问它的状态,都得排队喊一声 await。

小例子:

actor Counter {
  private var value = 0

  func increment() {
    value += 1
  }

  func current() -> Int {
    value
  }
}

let counter = Counter()

Task {
  await counter.increment()
  print(await counter.current()) // 1
}

这里的 await 看着有点麻烦,但它保证了多个任务同时来操作 counter 时,不会互相踩脚。对并发代码来说,这简直是救命的。

我自己的习惯

总结一下我的使用习惯:

  • struct:能用就用,特别是数据模型,天然安全,复制不怕。

  • class:当对象需要身份,或者和 UIKit / AppKit 打交道时。比如 ViewModel,我常常用 class + @ObservableObject。

  • actor:只在并发共享数据时才上,别乱用。因为 actor 内部访问全是异步,写业务逻辑的时候很容易链式 await,可读性会差一点。

所以我的优先级是:

👉 struct > class > actor。

就像 Swift 官方的建议:值类型优先,引用语义次之,并发安全最后兜底。

上一篇 Swift Codable 进阶:从基础到复杂类型