问题
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