【问题标题】:Storing array of heterogeneous keypaths存储异构键路径数组
【发布时间】:2021-03-06 21:34:39
【问题描述】:

我有一个类似的对象:

class MyObject {
  var title:String? = nil
  var value:Int? = nil
  var otherData:[String] = []
}

我正在尝试存储一个异构的更新数组,以便以后可以使用它们来设置值。像这样。

class SomeFormThing {
  func getUpdates() -> [WritableKeyPath<MyObject, Any> : Any] {
    var updates:[WritableKeyPath<MyObject, Any> : Any]
    updates[\MyObject.title] = "New Title"
    updates[\MyObject.value] = 0
    updates[\MyObject.value] = ["Other", "Values"]
    return updates
  }
}

class SyncManager {
  var form:SomeFormThing = SomeFormThing()
  var sync:MyObject = MyObject()
  func updateValues() {
    let updates = form.getUpdates()
    for key in updates.keys {
      sync[forKeyPath: key] = updates[key]
    }
  }
}

但是,这在 getUpdates() 中读取 Cannot convert value of type 'WritableKeyPath&lt;MyObject, String?&gt;' to expected argument type 'WritableKeyPath&lt;MyObject, Any&gt;' 的行中当然存在编译问题。

需要有一组异构的可写键路径,然后我可以使用它们来设置值。鉴于泛型通常是如何工作的,我不明白为什么 String 不能等同于 Any 泛型,因为该类型是向下转换的。

【问题讨论】:

  • Swift 泛型不是协变的,你不能强制转换它们。
  • @JoakimDanielson 我已经尝试过了。这会产生同样的问题。
  • @vadian 那么我将如何完成我正在尝试的事情,一个带有值的键路径的异构字典。
  • 基本上你不能。这一直是关键路径的问题,因为这是泛型的工作方式。当然,您可以键入擦除 - 这正是部分关键路径的用途 - 但这会带给您什么?基本上,您在这里想要做的是违背 Swift 的整体精神:关键路径为您提供类型安全,而您要求将其丢弃。
  • @matt 所以,本质上,除非您可以遵循 NSObject 并使用带有 .setValue 的字符串键,否则 Swift 无法根据对象的键:值对来反身地和动态地设置值?而且,在这种情况下,我不能使用 NSObject 的 .setValue,因为它不适用于可选项。这似乎是对基于反射的代码的巨大疏忽。

标签: swift keypaths swift-keypath


【解决方案1】:

Swift 集合不喜欢异构。此外,为了类型安全,键路径是泛型的,而泛型不是协变的。

所以你不能在一个集合中组合不同的类型——除非你类型擦除。当然,如果您这样做,您必须稍后un 键入erase,这很麻烦。这就像一个蟑螂汽车旅馆:包裹的类型进入但它们不会出来,除非你手动引出它们。

例如:

    struct MyObject {
      var title:String
      var value:Int
    }

    // okay, roaches go in...
    let kp1 = \MyObject.title
    let kp2 = \MyObject.value
    let dict : [PartialKeyPath<MyObject>:Any] = [kp1:"Yo", kp2:1]

    var object = MyObject(title: "Ha", value: 0)

    // but now what? how can we apply our `dict` entries to `object`? well...
    for (kp,val) in dict {
        switch (kp,val) {
        case (let kp2 as WritableKeyPath<MyObject,String>, let val2 as String):
            object[keyPath:kp2] = val2
        case (let kp2 as WritableKeyPath<MyObject,Int>, let val2 as Int):
            object[keyPath:kp2] = val2
        default:break
        }
    }
    print(object) // yep, we did it, sort of

所以你看,你可以做到,但你必须单独处理每一种可能的属性类型。您已经在 PartialKeyPath 的 Partial 和 Any 中隐藏了类型,现在将它们强制公开由您决定。

也就是说,我实际上这样做的方式是给 MyObject 一个通用的update 方法,并且一次只调用一个更改:

    struct MyObject {
        var title:String
        var value:Int
        mutating func update<T>(_ kp:WritableKeyPath<Self,T>, to val:T) {
            self[keyPath:kp] = val
        }
    }

所以:

    object.update(\.title, to: "Teehee")
    object.update(\.value, to: 42)

请注意,您不能直接从 dict 中的条目执行此操作,因为您已经隐藏了类型。泛型不会等到运行时才查看“真正”的类型是什么;它由编译器解析,所以类型必须是已知的——并且通过说 PartialKeyPath 和 Any 你已经使类型 un 已知。

【讨论】:

  • 感谢您的进一步解释。是的,必须处理单个属性类型作为演员将是一个巨大的痛苦。我已经考虑过使用闭包的类似泛型键入问题的建议,但这也有点混乱。实际上,我很惊讶 Swift 没有像 objc 那样的 setValue 类似的模式。我慢慢了解到 Swift 的反射功能不如其前身强大。
  • 查看我对这个问题的(新)评论。你为什么惊讶?您要求做的事情与 Swift 的整个精神背道而驰。如果你问我,我们很幸运拥有关键路径。现在,就是说,我当然已经完成了对象视图关键路径的更新;但您只需要使用泛型并一一获取。
  • 请阅读。是的,Keypaths 在技术上不是反射,但它们作为处理类似反射功能的 Swift 方式出售。不幸的是,苹果的旗舰语言在这方面缺乏。
  • 好吧,如果你一次拿一个而不是像那样制作一个集合,你可以继续调用一个通用的update 方法。这就是我实际上会做的。
  • 是的,我正在开发一个远程同步管理器,它允许临时对象(非同步)和在需要时应用的更新数组(在表单提交时),然后将异步触发到 API .因此,用户可以维护对象的运行编辑,而不必等待 API 请求返回以继续。这有点让人头疼,但使用闭包或一个巨大的讨厌的更新开关来楔入我的对象可能是我唯一的选择。
猜你喜欢
  • 1970-01-01
  • 2013-12-07
  • 2020-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-09
  • 1970-01-01
  • 2017-05-20
相关资源
最近更新 更多