您提供的特定语法可能是不可能的。将内容打包到[String: Any] 中可能会丢失太多无法恢复的信息。但你的总体目标肯定是可能的。
重要的是input。我们将使用显式类型 ValueChange,而不是 [String: Any]:
let input = [
ValueChange(key: "b", changedTo: "test"),
ValueChange(key: "c", changedTo: 2),
]
像这样创建一个新类型允许我们捕获所有类型,并强制执行某些规则,特别是值是Equatable:
struct ValueChange {
init<Value: Equatable>(key: String, changedTo newValue: Value) {...}
}
我稍后会回到 ValueChange,但首先我想回到它的使用方式。由于您需要.getDiff(...) 语法,因此最好使用协议创建扩展:
protocol DictionaryDiffComputing {}
extension DictionaryDiffComputing {
func getDiff(_ changes: [ValueChange]) -> [String: Any] {
Dictionary(uniqueKeysWithValues:
changes.compactMap { $0.changedValue(self) })
}
}
协议没有要求。它只是说“这些类型具有getDiff 方法”。如果值已更改,此方法需要 ValueChange 为我们提供(String, Any) 元组。
这就是问题变得有趣的地方,我只是展示答案然后讨论它。
struct ValueChange {
let changedValue: (_ object: Any) -> (String, Any)? // (key, newValue)
init<Value: Equatable>(key: String, changedTo newValue: Value) {
self.changedValue = { object in
// Get the old value as an Any using Mirror
guard let oldAnyValue: Any = Mirror(reflecting: object)
.children
.first(where: { $0.label == key })?
.value
else {
assertionFailure("Unknown key: \(key)")
return nil
}
// Make sure it's the correct type
guard let oldValue = oldAnyValue as? Value else {
assertionFailure("Bad type for values (\(oldAnyValue)). Expected: \(Value.self)")
return nil
}
// Compare the values
return newValue != oldValue ? (key, newValue) : nil
}
}
}
这使用 Mirror 提取旧值以作为 Any 类型进行比较,然后将其转换为正确的 Value 类型。这就是通用init 的强大之处。由于我们知道编译时的类型,我们可以在这个闭包中捕获它,从外部世界中删除该类型,但能够在运行时使用它。
extension MyStruct: DictionaryDiffComputing {}
let myStruct = MyStruct()
myStruct.getDiff(input) // ["b": "test"]
我真正不喜欢这个答案的是它非常不安全。请注意对assertionFailure 的两次调用。 ValueChange 没有任何东西可以确保键存在或值是正确的类型。如果您更改名称或键入属性,您的程序将崩溃或行为不正确,并且编译器无法帮助您。
您可以使这更安全,代码更简单,代价是调用语法稍微冗长:
protocol DictionaryDiffComputing {}
struct ValueChange<Root> {
let changedValue: (_ object: Root) -> (String, Any)? // (key, newValue)
init<Value: Equatable>(key: String, keyPath: KeyPath<Root, Value>, changedTo newValue: Value) {
self.changedValue = { newValue != $0[keyPath: keyPath] ? (key, newValue) : nil }
}
}
extension DictionaryDiffComputing {
func getDiff(_ changes: [ValueChange<Self>]) -> [String: Any] {
Dictionary(uniqueKeysWithValues:
changes.compactMap { $0.changedValue(self) })
}
}
let myStruct = MyStruct()
let input: [ValueChange<MyStruct>] = [
ValueChange(key: "b", keyPath: \.b, changedTo: "test"),
ValueChange(key: "c", keyPath: \.c, changedTo: 2),
]
myStruct.getDiff(input)
如果您使用这种方法,您就知道该类型上存在该属性,并且该值是该属性的正确类型。您还可以获得一些额外的功能,因为您可以使用从该根类型开始的任何您喜欢的密钥路径。这意味着您可以执行以下操作:
ValueChange(key: "b_length", keyPath: \.b.count, changedTo: 4),
您可以通过向键名添加一些键路径的映射字典(例如 static var 协议要求)来清除 ValueChange 中对 key 的要求,但我不知道如何生成它自动,我不知道有什么好方法可以将键路径转换为适当的字符串。