我正在尝试通过指向该属性的指针来获取/设置对象的计算属性。我已经在下面添加了代码段和输出。
代码段的要点是,Foo
类具有计算属性bar
。 Mutator
类保留一个指针并具有一个计算属性value
,它只获取/设置它指向的值。因此,如果我创建f1: Foo
,然后创建引用m1: Mutator
的{{1}}对象,我认为设置f1.bar
也会设置m1.value
。它有时有效,但并非总是如此。
f1.bar1
如果我在Linux上运行上面的代码,我会得到以下输出:
//---------------------------------------------------------------------------
// 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)")
}
第一个输出块显示预期的行为。第二个块指向不同的地址,这是意外的。 Weirder仍然是,尽管有错误的地址,它读取正确的值。最后一个输出块具有与第二个块中相同的地址,但读取不同的初始值,但它确实设法正确设置和读回值。
我知道这可能是滥用计算属性和指针。但有人可以解释为什么它有时有效吗?为什么在函数中创建它会给它一个不同的地址?为什么在函数中读取它并在它返回后在地址相同时给出不同的答案?有没有办法使这项工作?
只是为了进一步混淆:上面是在Linux上运行。当我在Mac上尝试这个实验时,我会得到一些不同的结果,尽管有时它的整体观察仍然有效。
答案 0 :(得分:4)
这些都不是定义的行为。它可能会也可能不会产生预期的结果,或者它可能只是在运行时崩溃。
当你说
时let m1 = Mutator(name:"mf1", storage: &f1.bar)
Swift将分配一些内存并将其初始化为f1.bar
的getter返回的值。然后,指向此内存的指针将传递到Mutator
init
- 并且在通话结束后,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
参数来分配新值
虽然个人而言,我更喜欢第一种方法,尽管重复。
答案 1 :(得分:1)
一旦在不安全指针和其他内部引用有效的块或范围之外,Swift语言定义不要求它不移动(或重用)类对象实例属性使用的内存。
所以,在你的第二个和第三个案例中,对象(或它的一些属性)可能已被移动,你正在检查和(危险地)改变对象曾经存在的内存,以及某些完全不同类型的部分对象当前可能是通过陈旧(因而非常不安全)的指针。
因此,Swift编译器(知道移动内容的时间和位置)知道如何在实例中读取和写入属性。但是你(通过陈旧的指针)不会。
补充:如果你想做这种类型的东西,那么自己分配(和管理)内存(在Swift中 可能)。