iOS Swift 值语义与引用语义

举报
yd_223002268 发表于 2026/09/15 08:41:12 2026/09/15
【摘要】 iOS Swift 值语义与引用语义Swift 同时提供结构体、枚举和类。选择值类型还是引用类型,不只是语法偏好,它会影响状态共享、并发安全、复制成本和接口可读性。值语义强调每份数据独立变化,引用语义强调多个使用者观察同一个对象。先明确业务语义,再考虑性能,通常能得到更稳定的设计。 一、值类型意味着独立值结构体赋值后,修改新变量不会改变旧变量:struct UserProfile { ...

iOS Swift 值语义与引用语义

Swift 同时提供结构体、枚举和类。选择值类型还是引用类型,不只是语法偏好,它会影响状态共享、并发安全、复制成本和接口可读性。值语义强调每份数据独立变化,引用语义强调多个使用者观察同一个对象。先明确业务语义,再考虑性能,通常能得到更稳定的设计。

一、值类型意味着独立值

结构体赋值后,修改新变量不会改变旧变量:

struct UserProfile {
    var nickname: String
    var city: String
}

var original = UserProfile(nickname: "小林", city: "上海")
var editing = original
editing.nickname = "林林"

print(original.nickname)
print(editing.nickname)

这种行为适合表单草稿、请求参数、配置快照和界面展示模型。使用者不需要追踪“还有谁持有同一个对象”。

二、引用类型表示共享身份

类实例赋值后,两个变量指向同一个对象:

final class PlaybackSession {
    var position: TimeInterval = 0
}

let firstReference = PlaybackSession()
let secondReference = firstReference
secondReference.position = 12

print(firstReference.position)

播放会话、数据库上下文、系统控制器等具有唯一身份和生命周期的对象,适合引用语义。类还支持继承和析构逻辑,但也需要处理共享可变状态和循环引用。

三、不要仅按数据大小选择

“结构体小、类大”是过度简化。真正的问题是数据是否需要身份,以及修改是否应该被其他持有者立即观察。

可以用几个问题判断:

  • 两个内容完全相同的实例是否仍应被视为不同对象。
  • 修改一个变量时,其他变量是否应该同步变化。
  • 对象是否需要明确的创建和销毁生命周期。
  • 是否需要在多个位置共享可变状态。
  • 是否需要继承现有类体系。

答案更偏向独立快照时使用值类型,更偏向唯一身份时使用引用类型。

四、理解写时复制

Swift 标准集合具有值语义,但实现通常采用写时复制。多个变量读取同一份集合时可以共享底层存储,某一方修改时才真正复制。

var first = Array(0..<1_000)
var second = first

second.append(1_000)

赋值本身不一定立即复制全部元素,但修改可能触发较大成本。高频修改大型集合时,应通过测量确认复制行为,而不是因为它是值类型就假设没有开销。

五、自定义写时复制容器

需要同时提供值语义和共享存储时,可以用内部引用对象实现写时复制:

private final class Storage {
    var values: [Int]

    init(values: [Int]) {
        self.values = values
    }

    func copy() -> Storage {
        Storage(values: values)
    }
}

struct NumberBuffer {
    private var storage: Storage

    init(values: [Int]) {
        storage = Storage(values: values)
    }

    mutating func append(_ value: Int) {
        if !isKnownUniquelyReferenced(&storage) {
            storage = storage.copy()
        }
        storage.values.append(value)
    }
}

这种实现需要保证所有修改入口都执行唯一性检查,否则外部看到的值语义会被破坏。普通业务模型没有必要自行实现复杂存储。

六、不可变属性让语义更明确

结构体内部尽量使用 let,通过创建新值表达状态变化:

struct OrderState: Equatable {
    let orderId: String
    let status: Status
    let updateTime: Date

    func updating(status: Status, at time: Date) -> OrderState {
        OrderState(
            orderId: orderId,
            status: status,
            updateTime: time
        )
    }
}

不可变快照更容易比较、记录和跨线程传递,也能避免某个模块无意修改共享数据。

七、协议中的 mutating 语义

值类型方法需要修改自身时,要使用 mutating

protocol Resettable {
    mutating func reset()
}

struct SearchCondition: Resettable {
    var keyword = ""
    var category = "all"

    mutating func reset() {
        self = SearchCondition()
    }
}

这使调用方能够从方法签名看出操作会改变值。类实现同一协议时不需要 mutating,因为类实例本身通过引用修改。

八、闭包捕获中的差异

闭包捕获引用类型时,可能延长对象生命周期并形成循环引用。捕获可变值类型时,则要理解闭包何时复制、变量是否被装箱共享。

不要机械地给所有闭包添加弱捕获。应先确认闭包是否逃逸、由谁持有,以及目标对象在回调执行时是否应该继续存在。

九、并发环境中的选择

不可变值通常更容易在并发任务之间传递,因为任务拿到的是稳定快照。共享可变类则需要隔离或同步,防止多个执行上下文同时读写。

值类型也不是天然线程安全。如果内部包含引用存储,或者多个任务通过外部容器共同修改同一个变量,仍然会产生数据竞争。应遵循项目当前并发模型和编译器检查规则。

十、常见选择建议

  • 业务数据、请求参数、界面模型优先考虑结构体。
  • 有限状态集合优先考虑枚举。
  • 具有身份、生命周期或共享行为的对象使用类。
  • 需要继承系统控制器或视图时使用类。
  • 需要资源释放回调时使用类,并明确所有权。

这些是起点而不是绝对规则。现有项目若已经围绕某种模型建立成熟约定,应优先保持一致。

总结

值语义让数据变化局部化,引用语义让多个使用者共享同一身份。根据“是否需要身份”和“修改是否需要共享”选择类型,再关注复制成本、生命周期和并发隔离,可以让 Swift 模型更容易理解和维护。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。