当前位置: 首页 > 面试题库 >

关于字典访问的Swift语义

殳经略
2023-03-14
问题内容

我目前正在从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 ?

问题答案:

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
Array的下标不同,下标包含getter和称为的特殊访问器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在评论中指出的那样,可以将其简化为:

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()
}

As the values property now returns a special
Dictionary.Values
mutable collection that has a
subscript
with an addressor (see SE-0154
for more info on this change).

However, currently (with the version of Swift 4 that ships with Xcode 9 beta
5), this still makes a copy. This is due to the fact that both the
Dictionary and Dictionary.Values instances have a view onto the underlying
buffer – as the values computed property is just
implemented
with a getter and setter that passes around a reference to the dictionary’s
buffer.

So when calling the addressor, a copy of the dictionary’s buffer is triggered,
therefore leading to two views onto COWStruct‘s Buffer instance, therefore
triggering a copy of it upon change() being called.

I have filed a bug over this here. (
Edit: This has now been fixed
on master with the unofficial introduction of generalised accessors using
coroutines, so will be fixed in Swift 5 – see below for more info).

In Swift 4.1, Dictionary‘s subscript(_:default:) now uses an
addressor, so we can efficiently
mutate values so long as we supply a default value to use in the mutation.

For example:

dict["key", default: COWStruct()].change() // No copy

The default: parameter uses @autoclosure such that the default value isn’t
evaluated if it isn’t needed (such as in this case where we know there’s a
value for the key).

Swift 5 and beyond

With the unofficial introduction
of generalised
accessors in Swift 5, two new underscored accessors have been introduced,
_read and _modify which use coroutines in order to yield a value back to
the caller. For _modify, this can be an arbitrary mutable expression.

The use of coroutines is exciting because it means that a _modify accessor
can now perform logic both before and after the mutation. This allows them to
be much more efficient when it comes to copy-on-write types, as they can for
example deinitialise the value in storage while yielding a temporary mutable
copy of the value that’s uniquely referenced to the caller (and then
reinitialising the value in storage upon control returning to the callee).

标准库已更新,很多以前低效的API来使用新的_modify访问-
这包括Dictionarysubscript(_:)现在可以产生独特的参考价值主叫(使用deinitialisation招我上面提到的)。

这些变化的结果意味着:

dict["key"]?.change() // No copy

无需在Swift
5中进行复制就可以执行值的突变(您甚至可以使用主快照亲自尝试一下)。



 类似资料:
  • 问题内容: 我的应用程序中有一个非常复杂的数据结构,需要对其进行操作。我试图跟踪玩家在他们的花园中有多少种错误。有十种错误,每种错误都有十种模式,每种模式都有十种颜色。所以可能有1000个独特的错误,我想追踪玩家每种类型的错误数量。嵌套的字典如下所示: 我没有使用此语法的任何错误或投诉。 当我想增加播放器的错误收集时,请执行以下操作: 我收到此错误: 字符串不能转换为’DictionaryInde

  • 如何在Swift中到达以下字典中的第二个整数(键)?而且,当我这样做的时候,我怎样才能找到第二个字典里的字串呢? 我尝试编写(例如)var test:Int=activeCustomers[1][1],但这不起作用。它说字典里没有一个叫下标的成员。

  • Swift 4中的字典是用于存储相同类型的无序值列表。 Swift 4进行了严格的检查,不允许在字典中输入错误的类型。 Swift 4字典使用称为键的唯一标识符来存储值,通过相同的键引用和查找值。与数组中的项目不同,字典中的项目没有指定的顺序。 当需要根据标识符(键)查找值时,可以使用字典。 字典键可以是整数,也可以是字符串,但它在字典中作为键是唯一的。 如果将创建的字典分配给变量,则它始终是可变

  • Swift 字典用来存储无序的相同类型数据的集合,Swift 字典会强制检测元素的类型,如果类型不同则会报错。 Swift 字典每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。 和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 Swift 字典的key

  • 关于指针的经典语录 指针也是一种数据类型,指针的数据类型是指它所指向内存空间的数据类型 间接赋值*p是指针存在的最大意义 理解指针必须和内存四区概念相结合 应用指针必须和函数调用相结合(指针做函数参数)。指针是子弹,函数是枪管;子弹只有沿着枪管发射才能显示它的威力;指针的学习重点不言而喻了吧。接口的封装和设计、模块的划分、解决实际应用问题;它是你的工具。 指针指向谁就把谁的地址赋给指针 指针指向谁

  • 问题内容: 我正在使用[UIImage:UIImage]类型的快速字典,并且正在尝试查找给定值的特定键。在Objective- C中,我可以使用allKeysForValue,但是Swift字典似乎没有这种方法。我应该使用什么? 问题答案: Swift 3:针对双射字典的特殊情况,性能更高 如果反向字典查找用例涵盖了在键和值之间具有一对一关系的双射字典,那么穷举收集操作的另一种方法是使用更快的短路