Swift Unsafemeutablepointer.pointee和didSet属性
在我创建的结构(在Xcode 10.1、Swift 4.2上)中观察到的属性上使用UnsafemeutablePointer,我得到了一些意外的行为。请参见以下游乐场代码:Swift Unsafemeutablepointer.pointee和didSet属性,swift,pointers,unsafemutablepointer,Swift,Pointers,Unsafemutablepointer,在我创建的结构(在Xcode 10.1、Swift 4.2上)中观察到的属性上使用UnsafemeutablePointer,我得到了一些意外的行为。请参见以下游乐场代码: struct NormalThing { var anInt = 0 } struct IntObservingThing { var anInt: Int = 0 { didSet { print("I was just set to \(anInt)")
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"
从表面上看,在指向观察到的属性的UnsafemeutablePointer上修改指针对象实际上并没有修改属性的值。此外,将指针指定给属性的行为将触发didSet操作。我在这里遗漏了什么?我很确定问题在于你所做的是非法的。您不能仅仅声明一个不安全的指针并声称它指向一个结构属性的地址。(事实上,我甚至不明白为什么您的代码首先要编译;编译器认为这是什么初始值设定项?)给出预期结果的正确方法是请求指向该地址的指针,如下所示:
struct IntObservingThing {
var anInt: Int = 0 {
didSet {
print("I was just set to \(anInt)")
}
}
}
withUnsafeMutablePointer(to: &intObservingThing.anInt) { ptr -> Void in
ptr.pointee = 20 // I was just set to 20
}
print(intObservingThing.anInt) // 20
每当您看到像
unsafemeutablepointer(&intobservingting.anInt)
这样的构造时,您都应该非常小心它是否会表现出未定义的行为。在绝大多数情况下,它会
首先,让我们详细分析一下这里发生的事情UnsafemeutablePointer
没有任何接受inout
参数的初始化器,那么此调用的是哪个初始化器?好的,编译器有一个特殊的转换,它允许&
前缀参数转换为指向表达式引用的“存储”的可变指针。这称为inout到指针的转换
例如:
func foo(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 1
}
var i = 0
foo(&i)
print(i) // 1
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
func bar() {
var i = 0
let ptr = foo(&i)
// Optimiser could de-initialise `i` here.
// ... making this undefined behaviour!
ptr.pointee += 1
}
var i: Int = 0 {
willSet(newValue) {
print("willSet to \(newValue), oldValue was \(i)")
}
didSet(oldValue) {
print("didSet to \(i), oldValue was \(oldValue)")
}
}
这仍然有效,那么指针指向的存储是什么呢?要解决此问题,编译器:
i
的getter,并将结果值放入临时变量中foo
的调用i
的settervar j = i // calling `i`'s getter
foo(&j)
i = j // calling `i`'s setter
希望从这个例子中可以清楚地看到,这对传递到foo
的指针的生命周期施加了一个重要的约束–它只能用于在调用foo
期间改变i
的值。尝试转义指针并在调用foo
后使用它将导致仅修改临时变量的值,而不会修改i
例如:
func foo(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 1
}
var i = 0
foo(&i)
print(i) // 1
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
func bar() {
var i = 0
let ptr = foo(&i)
// Optimiser could de-initialise `i` here.
// ... making this undefined behaviour!
ptr.pointee += 1
}
var i: Int = 0 {
willSet(newValue) {
print("willSet to \(newValue), oldValue was \(i)")
}
didSet(oldValue) {
print("didSet to \(i), oldValue was \(oldValue)")
}
}
func foo(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 1
}
var i = 0
foo(&i)
print(i) // 1
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
func bar() {
var i = 0
let ptr = foo(&i)
// Optimiser could de-initialise `i` here.
// ... making this undefined behaviour!
ptr.pointee += 1
}
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)
}
}
好的,但是我的函数
foo
的示例与调用unsafemeutablepointer
初始化器的示例有什么关系呢?嗯,unsafemeutablepointer
(由于符合大多数标准库指针类型所遵循的带下划线的\u-Pointer
协议)
此初始化器实际上与foo
函数相同–它接受unsafemeutablepointer
参数并返回它。因此,当您执行unsafemeutablepointer(&intobservingting.anInt)
时,您正在转义inout-to-pointer转换生成的指针,正如我们所讨论的,只有在存储的全局或静态变量上使用它而不使用观察器时,该指针才有效
因此,总结一下:
var intObservingThing = IntObservingThing(anInt: 0)
var otherPtr = UnsafeMutablePointer(&intObservingThing.anInt)
// "I was just set to 0."
otherPtr.pointee = 20
这是一种未定义的行为。inout-to-pointer转换生成的指针仅在调用UnsafeMutablePointer
的初始化器期间有效。之后尝试使用它会导致未定义的行为。同样,如果您希望对的作用域指针访问进入bservingting.anInt
,您希望使用和不可配置指针(to:)
(这很可能会转变为一个错误)在这种不可靠的inout-to-pointer转换中会发出。不幸的是,我最近没有太多的时间来研究它,但一切进展顺利,我的目标是在新的一年里开始推动它向前发展,并希望它能在Swift 5.x版本中发布
此外,值得注意的是,尽管编译器目前无法保证以下各项的良好定义行为:
var normalThing = NormalThing(anInt: 0)
var ptr = UnsafeMutablePointer(&normalThing.anInt)
ptr.pointee = 20
从上面的讨论来看,这很可能是编译器在未来版本中保证良好定义的行为的东西,因为基本(
normalThing
)是一个不带观察者的struct
的脆弱存储全局变量,而anInt
是一个脆弱的存储属性,没有观察者。“你不能仅仅声明一个不安全的指针并声称它指向一个结构属性的地址。”-你能指出任何说明这一点的文档吗?我不想挑战它,但我想看看这条语句背后的原因。关于编译器认为这是什么样的初始值设定项-为什么它认为这不是指向UnsafemtablePointer的初始值设定项?让我们这样看。您可以编写不可配置的指针(&intobservingting.anInt)
。现在,如果这是合法的,它应该可以与UnsafeMutablePointer.init(&intobservingting.anInt)
互换。但是如果你这样写的话,编译器就会阻塞——这是正确的。因此,我认为您的表达式,由于编译器中的一些错误,正在悄悄地通过编译器的保护。我承认我有点含糊不清,但我确实认为我已经解释了h