Swift:通过指针访问计算属性

Swift:通过指针访问计算属性,swift,Swift,我一直在尝试通过指向某个对象的指针来获取/设置该对象的计算属性。我在下面包含了代码片段和输出 代码片段的要点是有一个类Foo,它具有一个计算属性bar。Mutator类保留一个指针,并具有一个计算属性value,该属性仅获取/设置它所指向的值。因此,如果我创建f1:Foo,然后创建一个引用f1.bar的m1:Mutator对象,我会认为设置m1.value也会设置f1.bar1。它有时有效,但并不总是有效 //-------------------------------------------

我一直在尝试通过指向某个对象的指针来获取/设置该对象的计算属性。我在下面包含了代码片段和输出

代码片段的要点是有一个类
Foo
,它具有一个计算属性
bar
Mutator
类保留一个指针,并具有一个计算属性
value
,该属性仅获取/设置它所指向的值。因此,如果我创建
f1:Foo
,然后创建一个引用
f1.bar
m1:Mutator
对象,我会认为设置
m1.value
也会设置
f1.bar1
。它有时有效,但并不总是有效

//---------------------------------------------------------------------------
// Class definitions

class Foo
{
    private var data = [String: Double]()

    var bar: Double? 
    {
        get { return self.data["bar"] }
        set { self.data["bar"] = newValue }
    }       

    init(_ key: String, _ val: Double)
    {
        self.data[key] = val
    }
}

class Mutator
{
    let name: String
    let storage: UnsafeMutablePointer<Double?>

    var value: Double?
    {
        get { return self.storage.pointee }
        set { self.storage.pointee = newValue}
    }

    init(name: String, storage: UnsafeMutablePointer<Double?>)
    {
        self.name = name
        self.storage = storage
    }

}

//---------------------------------------------------------------------------
// Create and display mutators directly

print("-\nCreate and display mutator directly")

let f1 = Foo("bar", 1.1)
let f2 = Foo("bar", 2.2)
let f3 = Foo("bar", 3.3)

let m1 = Mutator(name:"mf1", storage: &f1.bar) // Or, let m1 = Mutator(name:"f1", storage: UnsafeMutablePointer<Double?>(&f1.bar))
let m2 = Mutator(name:"mf2", storage: &f2.bar)
let m3 = Mutator(name:"mf3", storage: &f3.bar)

var before = m1.value
m1.value = 199.1
var after = m1.value
print("\(m1.name): before=\(before), after=\(after) @ \(m1.storage)")   

before = m2.value
m2.value = 299.2
after = m2.value
print("\(m2.name): before=\(before), after=\(after) @ \(m2.storage)")

before = m3.value
m3.value = 299.2
after = m3.value
print("\(m3.name): before=\(before), after=\(after) @ \(m3.storage)")

//---------------------------------------------------------------------------
// Create mutators inside function

func createMutators() -> [Mutator]
{   
    print("-\nIn createMutators function ...")

    let m1 = Mutator(name:"mf1", storage: &f1.bar)
    let m2 = Mutator(name:"mf2", storage: &f2.bar)  
    let m3 = Mutator(name:"mf3", storage: &f3.bar)

    print("\(m1.name)=\(m1.value) @ \(m1.storage)")
    print("\(m2.name)=\(m2.value) @ \(m2.storage)")
    print("\(m3.name)=\(m3.value) @ \(m3.storage)")

    return [m1, m2, m3] 
}

let mutator = createMutators()

//---------------------------------------------------------------------------
// Display mutators returned by function

print("-\nDisplay mutator returned by function")
for m in mutator
{
    let before = m.value
    m.value = 10.0 + (before ?? Double.nan)
    let after = m.value

    print("\(m.name): before=\(before), after=\(after) @ \(m.storage)") 
}
第一个输出块显示预期的行为。第二个块指向不同的地址,这是意外的。更奇怪的是,尽管有错误的地址,它读取的是正确的值。最后一个输出块与第二个块具有相同的地址,但读取不同的初始值,尽管它确实能够正确设置和读取值

我知道这可能是对计算属性和指针的滥用。但有人能解释为什么有时候它会起作用吗?为什么在函数中创建它会给它一个不同的地址?当地址相同时,为什么在函数中读取和返回后会给出不同的答案?有没有办法让这一切顺利进行


只是为了让事情更加混乱:以上是在Linux上运行的。当我在mac上尝试这个实验时,我得到了一些不同的结果,尽管有时它可以工作的总体观察结果仍然是正确的。

Swift语言定义不要求它不移动(或重用)类对象实例属性使用的内存,一旦超出块或范围,不安全指针和其他内部引用有效

因此,在第二种和第三种情况下,对象(或其某些属性)可能已被移动,您正在通过过时(因此非常不安全)的指针检查和(危险地)更改对象以前所在的内存,以及某些完全不同类型的对象的一部分当前所在的内存

因此,Swift编译器(知道何时何地移动内容)知道如何读取和写入实例内的属性。但你(通过陈旧的指针)没有


补充:如果你想做这类事情,那么自己分配(并管理)内存(这在Swift中是可能的)。它可能会也可能不会产生预期的结果,或者可能只是在运行时崩溃

当你说

let m1 = Mutator(name:"mf1", storage: &f1.bar)
Swift将分配一些内存并将其初始化为
f1.bar
的getter返回的值。然后,指向该内存的指针将被传递到
Mutator
init
–调用后,Swift将调用
f1.bar
的setter,其中包含它分配的内存内容(可能已更改)

然后将释放该内存–指针现在不再有效。读取和写入其
指针对象将产生未定义的行为。因此,您不应该在调用
Mutator
的初始化器之后保留指针

获取所需行为的一种方法是使用两个闭包获取和设置
f1.bar
,这两个闭包都捕获
f1
。这样可以确保只要闭包有效,对
f1
的引用就保持有效

例如:

struct Mutator<T> {

    let getter: () -> T
    let setter: (T) -> Void

    var value: T {
        get {
            return getter()
        }
        nonmutating set {
            setter(newValue)
        }
    }

    init(getter: @escaping () -> T, setter: @escaping (T) -> Void) {
        self.getter = getter
        self.setter = setter
    }
}
尽管这种方法的一个缺点是重复获取和设置的值(
f1.bar
)。另一种实现方式是使用带有函数参数的单个闭包,该函数参数接受一个
inout
参数,返回(可能经过变异的)值

struct Mutator{
让getter:()->T
让setter:(T)->Void
var值:T{
得到{
返回getter()
}
非交换集{
setter(新值)
}
}
init(mutator:@escaping((inout T)->T)->T){
//一个函数,当它被应用时,将用一个函数输入调用mutator
//它只返回调用者传递的inout参数。
吸气剂={
变异子{$0}
}
//一个函数,当应用一个给定的新值时,将调用mutator
//使用一个函数,该函数将设置调用方传递的inout参数
//设置为新值,然后将返回该值
//(但被外部函数忽略)
setter={newValue in
_=mutator{$0=newValue;返回$0}
}
}
}
// ...
设f1=Foo(“bar”,1.1)
设m1=Mutator{$0(&f1.bar)}
getter现在只应用传递的函数,返回传递的
inout
参数(本例中为
f1.bar
),setter使用此
inout
参数来分配新值


尽管我个人喜欢第一种方法,尽管有重复。

这是UnsafemeutablePointer中的不安全之处。:)很难看出您想要在什么环境下进行,本质上是在任何涉及Swift/iOS环境的情况下进行“性能编程”。如果这是业余爱好(这很好),(去吧)[!:)正如hotpaw所解释的,即使在Swift中,你也可以抓取一块ram内存,自己动手——这也许就是你正在走的路?享受吧。是的,只是四处玩玩和试验。谢谢!回答得很好,谢谢!我最初的实现与你的第一个建议非常相似;指针方法就是用e第二个实现是我没有想到的一个有趣的实现。
struct Mutator<T> {

    let getter: () -> T
    let setter: (T) -> Void

    var value: T {
        get {
            return getter()
        }
        nonmutating set {
            setter(newValue)
        }
    }

    init(getter: @escaping () -> T, setter: @escaping (T) -> Void) {
        self.getter = getter
        self.setter = setter
    }
}
class Foo {
    private var data = [String : Double]()

    var bar: Double? {
        get { return self.data["bar"] }
        set { self.data["bar"] = newValue }
    }

    init(_ key: String, _ val: Double) {
        self.data[key] = val
    }
}


let f1 = Foo("bar", 1.1)
let m1 = Mutator(getter: { f1.bar }, setter: { f1.bar = $0 })

let before = m1.value
m1.value = 199.1

print("m1: before = \(before as Optional), after = \(m1.value as Optional)")
print("f1 after = \(f1.bar as Optional)")

// m1: before = Optional(1.1000000000000001), after = Optional(199.09999999999999)
// f1 after = Optional(199.09999999999999)
struct Mutator<T> {

    let getter: () -> T
    let setter: (T) -> Void

    var value: T {
        get {
            return getter()
        }
        nonmutating set {
            setter(newValue)
        }
    }

    init(mutator: @escaping ((inout T) -> T) -> T) {

        // a function, which when applied, will call mutator with a function input
        // that just returns the inout argument passed by the caller.
        getter = {
            mutator { $0 }
        }

        // a function, which when applied with a given new value, will call mutator
        // with a function that will set the inout argument passed by the caller
        // to the new value, which will then be returned 
        // (but ignored by the outer function)
        setter = { newValue in
            _ = mutator { $0 = newValue; return $0 }
        }
    }
}

// ...

let f1 = Foo("bar", 1.1)
let m1 = Mutator { $0(&f1.bar) }