草庐IT

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

coder 2023-09-12 原文

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

该片段的要点是有一个类 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

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

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

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

最佳答案

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

当你说

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:通过指针访问计算属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42798354/

有关Swift:通过指针访问计算属性的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  3. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  4. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  5. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  6. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  7. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  8. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  9. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  10. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

随机推荐