值和引用类型

原文:Value and Reference Types | Swift by Sundell

一般来说,Swift 类型可以分为两类------值类型和引用类型------这决定了它们在不同函数和其他代码范围之间的处理方式。使用值类型时,每个实例都作为一个值单独处理和变异,而引用类型实例每个都充当对对象的引用。让我们来看看这实际上意味着什么,以及一些实际影响。

让我们从引用类型开始,它在 Swift 中本质上意味着定义为 class 的类型。假设我们正在开发一个社交网络应用程序,并且我们想要定义一个类型来表示用户可以发布的帖子。如果我们选择使它成为一个类,它可能看起来像这样:

swift 复制代码
class Post {
    var title: String
    var text: String
    var numberOfLikes = 0

    init(title: String, text: String) {
        self.title = title
        self.text = text
    }
}

接下来,假设我们要编写一个函数,当用户按下某种形式的点赞按钮时可以调用该函数,这将增加帖子的 numberOfLikes 并显示确认 UI:

swift 复制代码
func like(_ post: Post) {
    post.numberOfLikes += 1
    showLikeConfirmation()
}

将上述两段代码放在一起,我们现在可以创建一个 Post 实例,将其传递给我们的 like 函数,并期望打印帖子的 numberOfLikes 将导致 1 显示在调试控制台中:

swift 复制代码
let post = Post(title: "Hello, world!", text: "...")
like(post)
print(post.numberOfLikes) // 1

到目前为止,一切都很好,而且总的来说,类(或引用类型)的行为方式通常非常直观------尤其是对于具有使用其他面向对象编程语言背景的开发人员而言。如果我们将一个对象传递给一个函数,那么该函数内发生的任何突变也会反映在它之外------因为我们总是引用原始实例,即使我们将一个对象传递给我们代码库的不同部分。

另一方面,值类型的行为完全不同。让我们保持我们的 Post 类型和以前完全一样,只是把它从一个类变成一个结构:

swift 复制代码
struct Post {
    var title: String
    var text: String
    var numberOfLikes = 0

    init(title: String, text: String) {
        self.title = title
        self.text = text
    }
}

随着上述变化的到位,编译器将迫使我们稍微修改我们的 like 函数------因为传递给函数的值默认是常量,这意味着它们不能以任何方式改变。因此,为了让我们能够增加传递的帖子的 numberOfLikes 属性,我们需要创建它的可变副本,如下所示:

swift 复制代码
func like(_ post: Post) {
    // 简单地将帖子重新分配给一个新的、可变的变量
    // 实际上会创建它的一个新副本
    var post = post
    post.numberOfLikes += 1
    showLikeConfirmation()
}

但是,问题在于,由于我们现在正在复制该值,因此我们在 like 函数范围内对其所做的任何更改都不会应用于我们传入的原始 Post 值------使我们之前的代码现在打印 0 而不是 1:

swift 复制代码
let post = Post(title: "Hello, world!", text: "...")
like(post)
print(post.numberOfLikes) // 0

解决上述问题的一种方法是使用 inout 关键字将 like 函数的 Post 参数转换为引用,即使它是值类型。这样,我们可以自由地改变函数内部的值,并且更改将应用于传入的原始值 - 就像使用引用类型时一样:

swift 复制代码
func like(_ post: inout Post) {
    post.numberOfLikes += 1
    showLikeConfirmation()
}

唯一的区别是,在调用栈点,我们现在需要使用 & 前缀传递 Post 值------这表明我们传递了一个值类型作为引用,再次导致 1 被打印为喜欢的数量:

swift 复制代码
var post = Post(title: "Hello, world!", text: "...")
like(&post)
print(post.numberOfLikes) // 1

虽然 inout 确实有其用例,但可以说完全接受值类型的概念比将它们视为引用更好(如果我们需要引用,为什么不坚持使用类呢?)。为此,让我们让我们的 like 函数返回传递的帖子的新的、更新的副本------而不是尝试改变原始值:

swift 复制代码
func like(_ post: Post) -> Post {
    var post = post
    post.numberOfLikes += 1
    showLikeConfirmation()
    return post
}

完成上述更改后,我们现在可以简单地将调用 like 的结果分配给我们原来的 post 变量,以确保我们的外部范围反映了我们函数中所做的更改:

swift 复制代码
var post = Post(title: "Hello, world!", text: "...")
post = like(post)
print(post.numberOfLikes) // 1

我们还可以更进一步,为 Post 添加一个mutating API 以增加喜欢的数量,使 post 值能够自行变异:

swift 复制代码
extension Post {
    mutating func like() {
        numberOfLikes += 1
    }
}

使用上述方法,我们还可以创建另一个便利 API,它执行一次喜欢帖子所需的复制和变异:

Mutating and non-mutating Swift contexts | Swift by Sundell

swift 复制代码
extension Post {
    func liked() -> Post {
        var post = self
        post.like()
        return post
    }
}

完成上述操作后,我们现在可以回到我们的 like 函数,并使用我们新的便利 API 将其简化为仅充当显示确认 UI 和执行模型突变的包装器:

swift 复制代码
func like(_ post: Post) -> Post {
    showLikeConfirmation()
    return post.liked()
}

何时使用值类型与引用类型在很大程度上取决于我们希望类型具有什么样的语义。将其视为一个简单值是否更有意义?只能在特定情况下进行局部变异,还是每个实例具有实际身份并作为参考传递更有意义?

不管我们最终选择什么,倾向于我们选择的语义------并相应地调整我们的代码------而不是与类型系统对抗通常是一个更好的主意。

谢谢阅读!🚀

相关推荐
传奇开心果编程10 小时前
【SwiftUI提高练中学】第13课 实时活动与灵动岛:ActivityKit 实战
学习·ui·ios·swiftui·swift
AC赳赳老秦16 小时前
数据采集全链路审计留痕:用 OpenClaw 实现合规审计与追溯
开发语言·汇编·python·php·swift·deepseek·openclaw
传奇开心果编程1 天前
【SwiftUI入门练中学】第17课 通知与后台任务
学习·ui·ios·swiftui·swift
逐流人2 天前
Ceph对象存储与文件系统存储管理:从RADOS网关、S3与Swift到CephFS快照同步
linux·运维·ceph·云原生·云计算·swift·rados
传奇开心果编程3 天前
【SwiftUI入门练中学】第7课 动画与手势:让界面更灵动
学习·macos·ui·ios·swiftui·swift
传奇开心果编程3 天前
【SwiftUI娓娓道来】第3课:让界面活起来——交互与动画指南
学习·macos·ui·ios·swiftui·swift
用户38034165882974 天前
控制中心一键后台录音:我被自己的 Spike 骗了两次
ios·swift
东坡肘子4 天前
iPhone Duo 留给开发者的一个月 -- 肘子的 Swift 周报 #155
人工智能·swiftui·swift
茶底世界之下5 天前
导出进度不是一个百分比:AVFoundation 收尾阶段的状态建模
ios·swift
茶底世界之下5 天前
为什么媒体源不能在回调仍执行时提前报告 finish?
swift