【问题标题】:Swift: accessing computed property through pointerSwift:通过指针访问计算属性
【发布时间】:2017-03-14 23:22:46
【问题描述】:

我试图通过指向该属性的指针来获取/设置对象的计算属性。我在下面包含了代码 sn-p 和输出。

sn-p 的要点是有一个类Foo 具有一个计算属性barMutator 类保留一个指针,并有一个计算属性 value,它只是获取/设置它指向的值。所以,如果我创建f1: Foo,然后创建一个引用f1.barm1: 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 上运行上述代码,我会得到以下输出:

Create and display mutator directly
mf1: before=Optional(1.1000000000000001), after=Optional(199.09999999999999) @ 0x00007ffd38f82730
mf2: before=Optional(2.2000000000000002), after=Optional(299.19999999999999) @ 0x00007ffd38f82708
mf3: before=Optional(3.2999999999999998), after=Optional(299.19999999999999) @ 0x00007ffd38f826e0
-
In createMutators function ...
mf1=Optional(1.1000000000000001) @ 0x00007ffd38f82288
mf2=Optional(2.2000000000000002) @ 0x00007ffd38f82260
mf3=Optional(3.2999999999999998) @ 0x00007ffd38f82238
-
Display mutator returned by function
mf1: before=Optional(4.9406564584124654e-324), after=Optional(10.0) @ 0x00007ffd38f82288
mf2: before=Optional(6.9527664311957093e-310), after=Optional(10.0) @ 0x00007ffd38f82260
mf3: before=nil, after=Optional(nan) @ 0x00007ffd38f82238

第一个输出块显示预期的行为。第二个块指向不同的地址,这是出乎意料的。更奇怪的是,尽管地址错误,但它读取了正确的值。最后一个输出块与第二个块具有相同的地址,但读取不同的初始值,尽管它确实设法正确设置和读回值。

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

只是为了进一步混淆:以上是在 Linux 上运行的。当我在 Mac 上尝试这个实验时,我得到了一些不同的结果,尽管总体观察有时它有效。

【问题讨论】:

  • 这就是 UnsafeMutablePointer 中的 Unsafe。 :)
  • 很难看出你想在什么环境下做,本质上是在涉及 Swift / iOS 环境的任何事情中的“性能编程”。如果这是爱好(很棒),(去吧)[opencores.org]! :) 正如 hotpaw 所解释的那样,即使在 Swift 中,您也可以非常容易地抓住一块 ram 内存并自己搞砸——这也许就是您要走的路?享受吧。
  • 是的,只是在玩耍和试验。谢谢!

标签: swift


【解决方案1】:

这些都不是已定义的行为。它可能会也可能不会产生预期的结果,或者它可能只是在运行时崩溃。

当你说

let m1 = Mutator(name:"mf1", storage: &f1.bar)

Swift 将分配一些内存并将其初始化为f1.bar 的getter 返回的值。然后,指向该内存的指针将被传递到 Mutatorinit 中——在调用之后,Swift 将调用 f1.bar 的 setter 并使用它分配的内存的(可能已更改)内容。

然后该内存将被释放——指针现在不再有效。对其pointee 的读取和写入将产生未定义的行为。因此,您应该在调用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
    }
}

然后你可以像这样使用它:

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)

虽然这种方法的一个缺点是您获取和设置的值重复(在这种情况下为f1.bar)。一种替代实现是使用带有函数参数的单个闭包,该函数参数采用inout 参数,返回(可能是突变的)值。

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

getter 现在简单地应用传递的函数,返回传递的inout 参数(在本例中为f1.bar),setter 使用这个inout 参数来分配一个新值。

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

【讨论】:

  • 很好的答案,谢谢!我最初的实现与您的第一个建议非常相似;指针方法只是尝试使用 Swift 指针。第二个实现是我没想到的一个有趣的实现。
【解决方案2】:

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

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

所以 Swift 编译器(它知道它何时何地移动了东西)知道如何在实例中读取和写入属性。但你(通过陈旧的指针)没有。

补充:如果你想做这类事情,然后自己分配(和管理)内存(这在 Swift 中可能的)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-26
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多