gpt4 book ai didi

swift - UnsafeMutablePointer.pointee和didSet属性

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

在我创建的结构中(在Xcode 10.1,Swift 4.2上)观察到的属性上使用UnsafeMutablePointer时,出现了一些意外的行为。请参阅以下游乐场代码:

struct NormalThing {
var anInt = 0
}

struct IntObservingThing {
var anInt: Int = 0 {
didSet {
print("I was just set to \(anInt)")
}
}
}

var normalThing = NormalThing(anInt: 0)
var ptr = UnsafeMutablePointer(&normalThing.anInt)
ptr.pointee = 20
print(normalThing.anInt) // "20\n"

var intObservingThing = IntObservingThing(anInt: 0)
var otherPtr = UnsafeMutablePointer(&intObservingThing.anInt)
// "I was just set to 0."

otherPtr.pointee = 20
print(intObservingThing.anInt) // "0\n"

看来,将UnsafeMutablePointer上的指针修改为观察到的属性实际上并不会修改该属性的值。同样,将指针分配给属性的操作会触发didSet操作。我在这里想念什么?

最佳答案

每当您看到类似UnsafeMutablePointer(&intObservingThing.anInt)的结构时,都应该非常警惕它是否会表现出不确定的行为。在绝大多数情况下,它会。

首先,让我们详细分析一下这里发生的事情。 UnsafeMutablePointer没有任何采用inout参数的初始化程序,那么此调用程序是什么?好的,编译器有一个特殊的转换,允许将&前缀的参数转换为指向表达式引用的“存储”的可变指针。这称为inout到指针的转换。

例如:

func foo(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 1
}

var i = 0
foo(&i)
print(i) // 1

编译器插入一个转换,该转换将 &i变成指向 i的存储的可变指针。好的,但是当 i没有任何存储空间时会发生什么?例如,如果计算得出该怎么办?
func foo(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 1
}

var i: Int {
get { return 0 }
set { print("newValue = \(newValue)") }
}
foo(&i)
// prints: newValue = 1

这仍然有效,因此指针指向的是什么存储空间?为了解决这个问题,编译器:
  • 调用i的getter,并将结果值放入一个临时变量中。
  • 获取指向该临时变量的指针,并将其传递给foo的调用。
  • 使用临时值中的新值调用i的setter。

  • 有效地执行以下操作:
    var j = i // calling `i`'s getter
    foo(&j)
    i = j // calling `i`'s setter

    希望从该示例可以清楚地看出,这对传递给 foo的指针的生存期施加了重要的限制-它只能用于在调用 i的过程中使 foo的值发生变化。尝试转义指针并在调用 foo之后使用它会导致仅修改临时变量的值,而不是 i

    例如:
    func foo(_ ptr: UnsafeMutablePointer<Int>) -> UnsafeMutablePointer<Int> {
    return ptr
    }

    var i: Int {
    get { return 0 }
    set { print("newValue = \(newValue)") }
    }
    let ptr = foo(&i)
    // prints: newValue = 0
    ptr.pointee += 1
    ptr.pointee += 1是在使用临时变量的新值调用 i的setter之后发生的,因此无效。

    更糟糕的是,它表现出不确定的行为,因为编译器不保证在对 foo的调用结束后,临时变量将保持有效。例如,优化程序可以在调用后立即将其取消初始化。

    好的,但是只要我们得到的指针指向未计算的变量,就应该能够在传递给调用的调用之外使用指针,对吗?不幸的是,事实证明,在进行出站到指针转换时,还有很多其他方法可以使自己陷入困境!

    仅举几例(还有更多!):
  • 与以前的临时变量类似,局部变量也有问题。编译器不保证它将一直保持初始化状态,直到声明它的作用域结束为止。优化器可以自由地对其进行早期初始化。

    例如:
    func bar() {
    var i = 0
    let ptr = foo(&i)
    // Optimiser could de-initialise `i` here.

    // ... making this undefined behaviour!
    ptr.pointee += 1
    }
  • 与观察者一起存储的变量是有问题的,因为在幕后它实际上被实现为在其setter中调用其观察者的计算变量。

    例如:
    var i: Int = 0 {
    willSet(newValue) {
    print("willSet to \(newValue), oldValue was \(i)")
    }
    didSet(oldValue) {
    print("didSet to \(i), oldValue was \(oldValue)")
    }
    }

    本质上是用于以下方面的语法糖:
    var _i: Int = 0

    func willSetI(newValue: Int) {
    print("willSet to \(newValue), oldValue was \(i)")
    }

    func didSetI(oldValue: Int) {
    print("didSet to \(i), oldValue was \(oldValue)")
    }

    var i: Int {
    get {
    return _i
    }
    set {
    willSetI(newValue: newValue)
    let oldValue = _i
    _i = newValue
    didSetI(oldValue: oldValue)
    }
    }
  • 类上的非最终存储属性存在问题,因为它可以被计算属性覆盖。

  • 而且,这甚至没有考虑依赖编译器中实现细节的情况。

    因此,仅编译器 可确保int到指针的转换on stored global and static stored variables without observers中的稳定且唯一的指针值。在任何其他情况下,尝试在传递了调用的调用后转义并使用inout-to-pointer转换中的指针将导致未定义的行为。

    好的,但是带有函数 foo的示例与您调用 UnsafeMutablePointer初始化程序的示例有什么关系?好吧, UnsafeMutablePointer has an initialiser that takes an UnsafeMutablePointer argument(作为符合大多数标准库指针类型所遵循的强调的 _Pointer协议(protocol)的结果)。

    该初始化程序实际上与 foo函数相同–它接受 UnsafeMutablePointer参数并返回它。因此,当您执行 UnsafeMutablePointer(&intObservingThing.anInt)时,就转义了从inout到指针的转换产生的指针-正如我们已经讨论的那样,仅当它用于没有观察者的存储的全局或静态变量时才有效。

    因此,总结一下:
    var intObservingThing = IntObservingThing(anInt: 0)
    var otherPtr = UnsafeMutablePointer(&intObservingThing.anInt)
    // "I was just set to 0."

    otherPtr.pointee = 20

    是不确定的行为。从inout到指针的转换产生的指针仅在对 UnsafeMutablePointer的初始化程序的调用期间有效。之后尝试使用它会导致不确定的行为。作为 matt demonstrates,如果要对 intObservingThing.anInt进行范围内的指针访问,则要使用 withUnsafeMutablePointer(to:)

    I'm actually currently working on implementing a warning(希望将转换为错误)将在这种不正确的inout到指针转换中发出。不幸的是,我最近没有太多时间来处理它,但是一切进展顺利,我的目标是在新的一年中开始 push 它的发展,并希望将其纳入Swift 5.x版本。

    另外,值得注意的是,尽管编译器当前不保证以下行为的定义良好:
    var normalThing = NormalThing(anInt: 0)
    var ptr = UnsafeMutablePointer(&normalThing.anInt)
    ptr.pointee = 20

    从对 #20467的讨论来看,由于基本( normalThing)是 struct的易碎存储全局变量而没有观察者,因此在将来的发行版中,编译器很可能会保证为这种行为定义良好的行为。 ,而 anInt是没有观察者的易碎存储属性。

    关于swift - UnsafeMutablePointer.pointee和didSet属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53892190/

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