簡體   English   中英

Swift 循環通過 [String: Any] 並獲得與 Struct 或 Class 屬性的差異

[英]Swift Loop through [String: Any] and get difference to Struct or Class properties

我有一個具有多個屬性的結構。 如何編寫一個 function,它采用 [String: Any] 類型的字典並創建另一個 [String: Any] 類型的字典,其中僅包含輸入字典的值與具有相同名稱的結構屬性值不同的鍵和值?

一個結構:

struct MyStruct {
   var a: Bool = false
   var b: String = "random"
   var c: Int = 2
}

所需的 function 調用:

let myStruct = MyStruct()
let input: [String: Any] = ["b": "test", "c": 2]
let result: [String: Any] = myStruct.getDiff(input)

示例輸入的期望結果:

result = ["b": "test"]

除了結構之外,如何將 [String: Any] 與 class 進行比較?

您提供的特定語法可能是不可能的。 將內容打包成[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,但首先我想 go 回到它的使用方式。 由於您需要.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),

您可以通過向鍵名添加一些鍵路徑的映射字典來清除ValueChangekey的要求(例如static var協議要求),但我不知道自動生成它的方法,我也不知道將鍵路徑轉換為適當字符串的任何好方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM