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)

关于使这个泛型超过类型的问题,这非常简单(我刚刚添加了&lt;Obj: AnyObject&gt; 并将对“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 (T.Type vs T) 中遇到问题【参考方案2】:

一种解决方案是将集合移动到一个块中

之前

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 文本某些部分的字体属性? [复制]

如何解决转义闭包捕获 Swift 中的“inout”参数?

如何在 swift 函数中返回 inout(引用)?

Swift 高级特性

Swift 3:UIImage 设置为模板图像并更改色调颜色时不显示图像

导致“无法将不可变作为 inout 传递”的 Swift 3 转换 [重复]