📌 开头先说结论
error: cannot use mutating member on immutable variable: 'self' is immutableinout 的真身是 copy-in / copy-out,不是简单"传指针"
mutating 方法会触发 willSet / didSet
下标 subscript 也能 mutating
nonmutating set:一个"不修改自己"的 setter
enum 的状态机写法
mutating 与 COW(写时复制) 的联动
protocol 里的 mutating 会让 class "偷渡"绕过 let
Swift 5/6 的独占访问(Exclusivity):mutating 会"霸占"整个 self
一、值 vs 引用:一切的起点
classPersonClass{var name = ”小李”}let p = PersonClass()p.name = ”大王” // ✅ class 是引用类型,let 只锁引用,属性随便改struct PersonStruct {var name = ”小李”}let p2 = PersonStruct()p2.name = ”大王” // ❌ struct 是值类型,let 锁整个对象,属性也不能动
二、mutating 是什么?
struct Counter {var count = 0mutating func increment() { count += 1 } // 不加 mutating 编译报错}var counter = Counter()counter.increment() // ✅
classCounterClass{var count = 0func increment() { count += 1 } // 不需要 mutating}
三、底层真相:inout 是 copy-in / copy-out,不是传指针
// 你写的:struct Counter {var count: Intmutating func increment() { count += 1 }}// 编译器大致生成:struct Counter {var count: Intfunc increment(_ self: inout Counter) {self.count = self.count + 1}}
调用 counter.increment() 时:1. 把 counter 当前的值【拷入】函数内部的 self2. 函数在 self 的副本上做所有修改3. 函数返回时,把修改后的 self【拷出】写回原变量 counter
四、mutating 会触发 willSet / didSet
struct User {var name: String {willSet { print(”willSet -> \(newValue)”) }didSet { print(”didSet <- \(oldValue)”) }}init(name: String) { self.name = name } // init 里赋值不触发观察器mutating func rename(to newName: String) {name = newName // ✅ 这里赋值会触发 willSet + didSet}}var u = User(name: ”小李”)u.rename(to: ”大王”)// 输出:// willSet -> 大王// didSet <- 小李
五、mutating subscript:下标也能"改自己"
struct Grid {private var cells: [Int]let size: Intinit(size: Int) {self.size = sizeself.cells = Array(repeating: 0, count: size * size)}mutating subscript(row: Int, col: Int) -> Int {get { cells[row * size + col] }set { cells[row * size + col] = newValue } // setter 需要 mutating}}var g = Grid(size: 3)g[1, 1] = 9 // ✅ 通过 mutating subscript 改自己print(g[1, 1]) // 9
六、nonmutating set:一个"不修改自己"的 setter
class Backing {var value = 0}struct Wrapper {private var backing: Backinginit(_ backing: Backing) { self.backing = backing }var value: Int {get { backing.value }nonmutating set { backing.value = newValue } // 没改 struct,改的是 backing 对象}}let w = Wrapper(Backing()) // w 是 letw.value = 42 // ✅ 因为 setter 是 nonmutating,let 也能调用print(w.value) // 42
七、enum 里的 mutating:状态机写法
enumNetworkState{case idlecase loadingcase success(data: Data)case failed(Error)mutating func transition(to next: NetworkState) {self = next // ✅ mutating 里可以给 self 整体赋值(重新构造)}}var state = NetworkState.idlestate.transition(to: .loading)state.transition(to: .success(data: Data()))
八、mutating 与 COW(写时复制)的联动
struct MyBuffer {private var _storage: ManagedBuffer // 内部是引用类型缓冲区mutating func append(_ x: Int) {// 当 _storage 被修改时,COW 检查 isKnownUniquelyReferenced// 若有其他副本共享,先复制缓冲区,再写入_storage.append(x)}}
九、protocol 里的 mutating:class 的"偷渡"陷阱
protocol Resettable {mutating func reset()}struct S: Resettable {var count = 0mutating func reset() { count = 0 } // struct 必须 mutating}class C: Resettable {var count = 0func reset() { count = 0 } // class 可省略 mutating}
let c: Resettable = C() // c 是 letc.reset() // ✅ 居然能调用!因为 C 是 class,reset 不会改引用
let s: any Resettable = S(count: 5) // 值类型包进存在型// s.reset() // ❌ 报错:s 是 let,值类型不能调 mutatinglet c: any Resettable = C() // 引用类型包进存在型c.reset() // ✅ 通过:class 不受 let 限制
十、Swift 5/6 独占访问:mutating 会"霸占"整个 self
struct Cell {var value = 0mutating func increment(by other: Int) {value += other}}var c = Cell()c.increment(by: c.value)// ❌ Error: overlapping accesses to 'c', but modification requires exclusive access
func swap(_ a: inout Int, _ b: inout Int) {}var x = 1swap(&x, &x) // ❌ Error: inout arguments are not allowed to alias each other
十一、性能:大 struct 调 mutating 的隐藏拷贝成本
classHolder{var point = Point(x: 0, y: 0) // struct 作为类的属性存储}// 当 Point 是拥有上千字段的大 struct,且通过类的属性间接访问:holder.point.move(dx: 1, dy: 1)// 真实发生:load 整个 point → 改 → store 整个 point 回 holder// 上千字段 = 上千次内存搬运,每次 mutating 方法调用都来一遍
十二、常见坑 & 反模式
❌ 坑 1:let 上调用 mutating
let p = Point(x: 0, y: 0)p.move(dx: 1, dy: 1) // ❌ 改用 var
❌ 坑 2:闭包里调 mutating self(真实报错)
struct S {var count = 0mutating func makeClosure() -> () -> Void {return {self.increment() // ❌ Error: escaping closure captures mutating 'self' parameter}}mutating func increment() { count += 1 }}
❌ 坑 3:mutating 里给 self 整体赋值
struct Rectangle {var width = 0, height = 0mutating func scale(by f: Int) {self = Rectangle(width: width * f, height: height * f) // ✅ 合法,重新构造}}
❌ 坑 4:误以为 class 里写 mutating 有意义
classFoo{var x = 0mutating func bar() { x += 1 } // ⚠️ 能编译,但 mutating 对 class 无意义}
十三、面试高频追问
💡 终极总结
mutating = 告诉编译器"我会改值语义的 self",底层走 inout(copy-in / copy-out)
class 不需要 mutating,因为引用类型的 self 是指针,改属性不动指针
mutating 方法会触发 willSet/didSet,会对 self 做独占访问,可能触发 COW
protocol 里的 mutating 对 class 是空约束——class 实现会绕过 let,是隐藏陷阱⚡ let 的 struct 调不了 mutating,改用 var
🚫 class 里别写 mutating(无意义)
🔒 闭包不能捕获 mutating self(inout 不能逃逸)
📊 大 struct 作为类属性被频繁 mutating,注意真实拷贝成本
struct 改自己要 mutating,本质是 inout 把"值的副本"借给方法改完写回;class 的 self 是指针,所以永远不用 mutating。
夜雨聆风