8-26.【内存管理机制】如何利用 isKnownUniquelyReferenced 判断是否可以原地修改对象?

44 阅读4分钟

在 Swift 中,利用 isKnownUniquelyReferenced 实现**原地修改(In-place Mutation)**是手动实现 写时复制(Copy-on-Write, COW) 的核心技术。

它的作用是检查一个引用类型(Class 实例)的强引用计数是否为 1。如果是 1,说明没有其他变量共享这块内存,直接修改它是安全的;如果大于 1,则必须先拷贝,以维持值类型的语义。


1. 核心逻辑步骤

实现一个具备 COW 特性的结构体,通常遵循以下模板:

  1. 内部包装:将数据存储在一个私有的 class 实例中。

  2. 写前检查:在 setmutating 方法中,调用 isKnownUniquelyReferenced

  3. 分支处理

    • 返回 true:直接修改内部 class 的属性(高性能)。
    • 返回 false:创建一个新的 class 实例并拷贝数据,再进行修改。

2. 代码实现示例

假设我们要创建一个处理大数据块的自定义结构体 DataBuffer

Swift

final class Storage {
    var bytes: [UInt8]
    init(bytes: [UInt8]) { self.bytes = bytes }
    
    // 提供一个方便克隆的方法
    func copy() -> Storage {
        return Storage(bytes: self.bytes)
    }
}

struct DataBuffer {
    private var _storage: Storage

    init(bytes: [UInt8]) {
        _storage = Storage(bytes: bytes)
    }

    var bytes: [UInt8] {
        get { _storage.bytes }
        set {
            // 关键点:检查 _storage 是否只有当前 DataBuffer 在引用
            if isKnownUniquelyReferenced(&_storage) {
                print("原地修改:引用计数为 1")
                _storage.bytes = newValue
            } else {
                print("执行拷贝:存在多个引用")
                _storage = _storage.copy()
                _storage.bytes = newValue
            }
        }
    }
}

运行结果演示:

Swift

var buffer1 = DataBuffer(bytes: [0, 1, 2])
var buffer2 = buffer1 // 此时两者共享同一个 _storage 实例

buffer1.bytes = [3, 4, 5] // 输出:执行拷贝(因为 buffer2 也在引用)
buffer1.bytes = [6, 7, 8] // 输出:原地修改(此时 buffer1 独占新申请的内存)

3. 重要注意事项与“坑”

A. 仅支持引用类型

isKnownUniquelyReferenced 的参数必须是一个 类(Class)实例的引用。如果你尝试传入一个结构体,编译器会报错。

B. 注意强引用的隐藏陷阱

如果在检查之前,你无意中创建了一个临时的强引用,该函数会返回 false

Swift

mutating func update() {
    let temp = _storage // 增加了一次引用计数!
    if isKnownUniquelyReferenced(&_storage) {
        // 这里永远不会执行,因为 temp 占着一个计数
    }
}

C. Objective-C 类的限制

isKnownUniquelyReferenced 不支持直接处理 NSObject 派生的类

  • 如果你的 Storage 继承自 NSObject,该函数会直接返回 false
  • 解决方法:始终使用纯 Swift 类(不继承自 NSObject)作为 COW 的后端存储。

4. 为什么要这么做?

  • 性能优化:避免不必要的深拷贝。对于拥有几万个元素的数组,拷贝一次的代价是 O(n)O(n),而原地修改是 O(1)O(1)
  • 值语义保证:确保结构体在被赋值给新变量后,对其中一个的修改不会影响另一个,保持数据的独立性。

总结

isKnownUniquelyReferenced 就像是一个内存门卫。它通过查看对象的“户口本”(引用计数),帮你决定是应该“粉刷旧房”(原地修改)还是“另起炉灶”(深拷贝)。

英文版

8-26. [Memory Management] How to use isKnownUniquelyReferenced to determine if an object can be mutated in-place?

In Swift, utilizing isKnownUniquelyReferenced to implement In-place Mutation is the core technique for manually implementing Copy-on-Write (COW) .

Its function is to check whether the strong reference count of a reference type (Class instance) is exactly 1. If it is 1, it means no other variables share this memory, and it is safe to modify it directly. If it is greater than 1, a copy must be made first to maintain value type semantics.


1. Core Logical Steps

To implement a struct with COW characteristics, you generally follow this template:

  1. Internal Packaging: Store the data in a private class instance.

  2. Pre-write Check: In the set observer or a mutating method, call isKnownUniquelyReferenced.

  3. Branch Handling:

    • Returns true: Modify the internal class properties directly (High performance).
    • Returns false: Create a new class instance, copy the data over, and then perform the modification.

2. Code Implementation Example

Suppose we want to create a custom struct DataBuffer that handles large blocks of data:

Swift

final class Storage {
    var bytes: [UInt8]
    init(bytes: [UInt8]) { self.bytes = bytes }
    
    // Provide a convenience method for cloning
    func copy() -> Storage {
        return Storage(bytes: self.bytes)
    }
}

struct DataBuffer {
    private var _storage: Storage

    init(bytes: [UInt8]) {
        _storage = Storage(bytes: bytes)
    }

    var bytes: [UInt8] {
        get { _storage.bytes }
        set {
            // Key Point: Check if _storage is uniquely referenced by this DataBuffer
            if isKnownUniquelyReferenced(&_storage) {
                print("In-place mutation: Reference count is 1")
                _storage.bytes = newValue
            } else {
                print("Executing copy: Multiple references exist")
                _storage = _storage.copy()
                _storage.bytes = newValue
            }
        }
    }
}

Execution Demo:

Swift

var buffer1 = DataBuffer(bytes: [0, 1, 2])
var buffer2 = buffer1 // Both now share the same _storage instance

buffer1.bytes = [3, 4, 5] // Output: Executing copy (because buffer2 also references it)
buffer1.bytes = [6, 7, 8] // Output: In-place mutation (buffer1 now owns the new memory exclusively)

3. Important Considerations and "Pitfalls"

A. Supports Reference Types Only

The argument for isKnownUniquelyReferenced must be a reference to a class instance. If you attempt to pass a struct, the compiler will throw an error.

B. Beware of Hidden Strong References

If you accidentally create a temporary strong reference before the check, the function will return false.

Swift

mutating func update() {
    let temp = _storage // Increments the reference count!
    if isKnownUniquelyReferenced(&_storage) {
        // This block will never execute because 'temp' holds a count
    }
}

C. Limitations with Objective-C Classes

isKnownUniquelyReferenced does not support direct handling of classes derived from NSObject.

  • If your Storage inherits from NSObject, the function will consistently return false.
  • Workaround: Always use pure Swift classes (those that do not inherit from NSObject) as the back-end storage for COW.

4. Why Do We Do This?

  • Performance Optimization: Avoid unnecessary deep copies. For an array with tens of thousands of elements, a copy costs O(n)O(n), whereas an in-place mutation is O(1)O(1).
  • Value Semantics Guarantee: Ensures that after a struct is assigned to a new variable, modifying one does not affect the other, maintaining data independence.

Summary

isKnownUniquelyReferenced acts like a memory gatekeeper. By checking the object's "residency permit" (reference count), it helps you decide whether to "repaint the old house" (mutate in-place) or "build a new one" (deep copy).