值和引用类型

原文: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()
}

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

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

谢谢阅读!🚀

相关推荐
一丝晨光1 天前
继承、Lambda、Objective-C和Swift
开发语言·macos·ios·objective-c·swift·继承·lambda
KWMax2 天前
RxSwift系列(二)操作符
ios·swift·rxswift
Mamong2 天前
Swift并发笔记
开发语言·ios·swift
小溪彼岸2 天前
【iOS小组件】小组件尺寸及类型适配
swiftui·swift
Adam.com3 天前
#Swift :回调地狱 的解决 —— 通过 task/await 来替代 nested mutiple trailing closure 来进行 回调的解耦
开发语言·swift
Anakki3 天前
【Swift官方文档】7.Swift集合类型
运维·服务器·swift
KeithTsui3 天前
集合论(ZFC)之 联合公理(Axiom of Union)注解
开发语言·其他·算法·binder·swift
東三城3 天前
【ios】---swift开发从入门到放弃
ios·swift
文件夹__iOS7 天前
[SwiftUI 开发] @dynamicCallable 与 callAsFunction:将类型实例作为函数调用
ios·swiftui·swift