Swift inout 如何在未更改时不复制回属性,以不触发对象设置器
Posted
技术标签:
【中文标题】Swift inout 如何在未更改时不复制回属性,以不触发对象设置器【英文标题】:Swift inout how to not copy back property when not changed, to not trigger objects setters 【发布时间】:2020-03-27 13:13:45 【问题描述】:according to the docs
通过将 inout 关键字放在 开始其参数定义。输入输出参数有一个值 传递给函数,由函数修改,并且是 从函数中传回以替换原始值。
但是如果根本没有改变结果如何不复制回来
我有一个数据库解析器
它仅在其值更改时分配attr
,但是,随着inout 的行为,始终设置传入的attr
(将我的数据库对象标记为脏并更改:/)
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)
有没有办法修改,所以只有传入的属性真的发生变化时才设置值?
【问题讨论】:
这种方法真的需要inout
吗?为什么?为什么不让它“纯”呢?还有你的问题到底是什么?
否;你不能在这里改变 inout 的行为。您将需要修改观察者。 (无论在看什么someAttr
都需要检查value != oldValue
。)
一个好的一般经验法则是:永远不要使用inout
,事实上,很少有场景值得使用它。主要是为了性能,例如在块密码的实现中,内存中分配的相同字节被写入数千次。让你的方法“纯粹”(不使用inout
,并且总是返回输出),也让你的代码更容易测试、调试和推理正确性:)
@Sajjon 如果没有 inout,您将无法做到这一点,您需要传入一个可变对象
@PeterLapisu 你误解了我的意思,我当然不是说从你的函数中删除inout
语句......而是完全改变你的解决方案,只使用纯函数......
【参考方案1】:
这就是inout
的工作原理。你无法改变这一点。 inout
的字面意思是“在开始时将值复制到函数中,并在结束时将值从函数中复制出来”。它不做任何分析来决定该值是否在运行时被触摸。
一种解决方案是检查观察者中的平凡集合,例如:
var someAttr: String?
didSet
guard someAttr != oldValue else return
...
作为另一种方法,我建议使用键路径。假设数据库对象是引用类型(类),我相信下面会做你想做的:
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
调用时间稍长,因为需要自己传递数据库:
importStringAttribute(json, "someAttr", db: myDBObject, attr: \.someAttr)
通过将方法附加到数据库可以使这更漂亮(尽管您仍然必须传递数据库,就像 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)
关于使这个泛型超过类型的问题,这非常简单(我刚刚添加了<Obj: AnyObject>
并将对“db”的引用更改为“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
【讨论】:
这看起来很有希望,但我很难在所有数据库类中概括(参数化)它:/ 这是对泛型(也可能是一个小协议)的完美使用。如果您有一个如何挣扎的例子,请告诉我,我将展示如何做到这一点。 (根据您的想法更新答案。) 感谢编辑...我在 ReferenceWritableKeyPath一种解决方案是将集合移动到一个块中
之前
importStringAttribute(json, "someAttr", &myDBObject.someAttr)
之后
importStringAttribute(json, "someAttr", myDBObject.someAttr) myDBObject.someAttr = $0
代码
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)
【讨论】:
以上是关于Swift inout 如何在未更改时不复制回属性,以不触发对象设置器的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Swift 中更改 UILabel 文本某些部分的字体属性? [复制]