gpt4 book ai didi

关于字典访问的 Swift 语义

转载 作者:搜寻专家 更新时间:2023-10-31 08:13:37 27 4
gpt4 key购买 nike

我目前正在 objc.io 上阅读优秀的 Advanced Swift 书籍,但遇到了一些我不明白的问题。

如果您在操场上运行以下代码,您会注意到在修改字典中包含的结构时,下标访问会生成一个副本,但随后字典中的原始值似乎被副本替换了。我不明白为什么。到底发生了什么?

另外,有没有办法避免复制?根据这本书的作者的说法,没有,但我只是想确定一下。

import Foundation

class Buffer {
let id = UUID()
var value = 0

func copy() -> Buffer {
let new = Buffer()
new.value = self.value
return new
}
}

struct COWStruct {
var buffer = Buffer()

init() { print("Creating \(buffer.id)") }

mutating func change() -> String {
if isKnownUniquelyReferenced(&buffer) {
buffer.value += 1
return "No copy \(buffer.id)"
} else {
let newBuffer = buffer.copy()
newBuffer.value += 1
buffer = newBuffer
return "Copy \(buffer.id)"
}
}
}

var array = [COWStruct()]
array[0].buffer.value
array[0].buffer.id
array[0].change()
array[0].buffer.value
array[0].buffer.id


var dict = ["key": COWStruct()]
dict["key"]?.buffer.value
dict["key"]?.buffer.id
dict["key"]?.change()
dict["key"]?.buffer.value
dict["key"]?.buffer.id

// If the above `change()` was made on a copy, why has the original value changed ?
// Did the copied & modified struct replace the original struct in the dictionary ?

enter image description here

最佳答案

dict["key"]?.change() // Copy

在语义上等同于:
if var value = dict["key"] {
value.change() // Copy
dict["key"] = value
}

该值被从字典中拉出,解包到一个临时的,变异的,然后放回字典中。

因为现在有两个对底层缓冲区的引用(一个来自我们的本地临时 value ,一个来自字典本身的 COWStruct 实例)——我们正在强制底层 Buffer 实例的副本,因为它不再被唯一引用。

那么,为什么不
array[0].change() // No Copy

做同样的事?肯定应该将元素从数组中拉出,进行变异,然后重新插入,替换之前的值?

不同之处在于,与 Dictionary 的下标(由 getter 和 setter 组成)不同, Arraysubscript comprises of a getter and a special accessor 称为 mutableAddressWithPinnedNativeOwner

这个特殊的访问器所做的是返回一个指向数组底层缓冲区中元素的指针,以及一个所有者对象,以确保缓冲区不会从调用者下释放。这样的访问器称为寻址器,因为它处理地址。

因此,当您说:
array[0].change()

你实际上是直接改变数组中的实际元素,而不是临时的。

这样的寻址器不能直接应用于 Dictionary 的下标,因为它返回一个 Optional ,并且底层值不作为可选值存储。所以它目前必须用临时文件解包,因为我们不能返回指向存储值的指针。

在 Swift 3 中,您可以通过在更改临时值之前从字典中删除值来避免复制 COWStruct 的底层 Buffer :
if var value = dict["key"] {
dict["key"] = nil
value.change() // No Copy
dict["key"] = value
}

现在只有临时对象可以查看底层 Buffer 实例。

而且,正如评论中的 @dfri points out 一样,这可以简化为:
if var value = dict.removeValue(forKey: "key") {
value.change() // No Copy
dict["key"] = value
}

节省散列操作。

此外,为了方便起见,您可能需要考虑将其变成扩展方法:
extension Dictionary {
mutating func withValue<R>(
forKey key: Key, mutations: (inout Value) throws -> R
) rethrows -> R? {
guard var value = removeValue(forKey: key) else { return nil }
defer {
updateValue(value, forKey: key)
}
return try mutations(&value)
}
}

// ...

dict.withValue(forKey: "key") {
$0.change() // No copy
}

在 Swift 4 中,您应该能够使用 values Dictionary 属性来执行值的直接更改:
if let index = dict.index(forKey: "key") {
dict.values[index].change()
}

由于 values 属性现在返回一个特殊的 Dictionary.Values 可变集合 that has a subscript 和一个寻址器(有关此更改的更多信息,请参阅 SE-0154)。

但是,目前(使用 Xcode 9 beta 5 附带的 Swift 4 版本),这仍然是一个副本。这是因为 DictionaryDictionary.Values 实例都可以查看底层缓冲区——作为 values 计算的属性 is just implemented 带有传递对字典缓冲区的引用的 getter 和 setter。

因此,在调用寻址器时,会触发字典缓冲区的副本,从而导致对 COWStructBuffer 实例的两个 View ,因此在调用 change() 时触发它的副本。

我有 filed a bug over this here 。 ( 编辑: 这个 has now been fixed on master 非官方介绍了使用协程的通用访问器,因此将在 Swift 5 中修复 - 有关更多信息,请参见下文)。

在 Swift 4.1 中, Dictionarysubscript(_:default:) now uses an addressor ,所以我们可以有效地改变值,只要我们提供一个默认值用于变异。

例如:
dict["key", default: COWStruct()].change() // No copy
default: 参数使用 @autoclosure,这样如果不需要默认值就不会计算它(例如在这种情况下,我们知道有一个键值)。

Swift 5 及更高版本

Swift 5 中的 the unofficial introductiongeneralised accessors 引入了两个新的下划线访问器, _read_modify,它们使用协程将值返回给调用者。对于 _modify ,这可以是任意可变表达式。

协程的使用令人兴奋,因为这意味着 _modify 访问器现在可以在突变之前和之后执行逻辑。这使它们在写入时复制类型时效率更高,因为它们可以例如取消初始化存储中的值,同时生成唯一引用到调用者的值的临时可变副本(然后重新初始化值在控制权返回给被调用者时在存储中)。

标准库已经更新了许多以前效率低下的 API,以利用新的 _modify 访问器 – this includes Dictionary 's subscript(_:) ,它现在可以为调用者生成唯一引用的值(使用我上面提到的去初始化技巧)。

这些变化的结果意味着:
dict["key"]?.change() // No copy

将能够执行值的更改,而无需在 Swift 5 中进行复制(您甚至可以自己尝试 with a master snapshot )。

关于关于字典访问的 Swift 语义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44632251/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com