Swift inout how to not copy back property when not changed, to not trigger objects setters

一个人想着一个人 提交于 2020-04-16 04:25:32

问题


according to the docs

You write an in-out parameter by placing the inout keyword at the start of its parameter definition. An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value.

But how to not copy back the result if it was not changed at all

I have a database parser which assigns the attr only when it's value changes, however, with the behavior of inout, the attr that is passed in is always set (marking my database object dirty and changed :/ )

func importStringAttribute(_ json: JSON, _ key: String, _ attr: inout String?) {
    if !json[key].exists() {
        return
    }
    if let v = json[key].string, v != attr {
        attr = v
    }
}

// the myDBObject.someAttr is always set 
importStringAttribute(json, "someAttr", &myDBObject.someAttr)

is there a way of modification, so the value is only set when the passed in attribute really changes?


回答1:


This is how inout works. You can't change that. inout literally means "copy the value into the function at the start and copy the value out of the function at the end." It doesn't do any analysis to decide whether the value was touched at runtime.

One solution is to check for trivial sets in the observer, for example:

var someAttr: String? {
    didSet {
        guard someAttr != oldValue else { return }
        ...
    }
}

As another approach, I suggest keypaths. Assuming that the database object is a reference type (class), I believe the following will do what you want:

func importStringAttribute(_ json: JSON, _ key: String, db: Database,
                           attr: ReferenceWritableKeyPath<Database, String?>) {
    if !json[key].exists() {
        return
    }
    if let v = json[key].string, v != db[keyPath: attr] {
        db[keyPath: attr] = v
    }
}

The call is slightly longer because you need to pass the database itself:

importStringAttribute(json, "someAttr", db: myDBObject, attr: \.someAttr)

That could be made a little prettier by attaching the method to the database (though you still have to pass the database, just as self):

extension Database {
    func importStringAttribute(_ json: JSON, _ key: String,
                               _ attr: ReferenceWritableKeyPath<Database, String?>) {
        if !json[key].exists() {
            return
        }
        if let v = json[key].string, v != self[keyPath: attr] {
            self[keyPath: attr] = v
        }
    }

}

myDBObject.importStringAttribute(json, "someAttr", \.someAttr)

To your question about making this generic over types, that's very straightforward (I just added <Obj: AnyObject> and changed the references to "db" to "obj"):

func importStringAttribute<Obj: AnyObject>(_ json: JSON, _ key: String, obj: Obj,
                           attr: ReferenceWritableKeyPath<Obj, String?>) {
    if !json[key].exists() {
        return
    }
    if let v = json[key].string, v != obj[keyPath: attr] {
        obj[keyPath: attr] = v
    }
}



回答2:


One solution would be also to move the set into a block

Before

importStringAttribute(json, "someAttr", &myDBObject.someAttr)

After

importStringAttribute(json, "someAttr", myDBObject.someAttr) { myDBObject.someAttr = $0}

code

func importStringAttribute(_ json: JSON, _ key: String, _ attr: String?, set:(_ value: String?)->()) {
    if !json[key].exists() {
        return
    }
    if let v = json[key].string, v != attr {
        set(v)
    }
}


来源:https://stackoverflow.com/questions/60886752/swift-inout-how-to-not-copy-back-property-when-not-changed-to-not-trigger-objec

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!