首页 > 解决方案 > 使用 @dynamicMemberLookup 访问 AnyCodable 值

问题描述

Objc.io有一个关于轻松改变无类型字典的精彩演讲,但问题是你不能轻易地持久化它们。我认为演讲可能在介绍之前已经发布@dynamicMemberLookup

AnyCodable看起来很棒,可以轻松编码/解码/保存简单的字典,但您不能轻松访问字典成员。

@dynamicMemberLookup我想知道将Swift 4.2 中的功能(例如:在此示例中)添加到 AnyCodable是否可能/可行,如果可以,如何?最终目标是访问/改变一个无类型的数组或字典并将它们持久化。

所以,我试着这样做:

@dynamicMemberLookup
public struct AnyCodable: Codable {
    public let value: Any

    public init<T>(_ value: T?) {
        self.value = value ?? ()
    }

    subscript(dynamicMember member: String) -> AnyCodable? {
        switch self.value {
        case let dictionary as [String: Any?]:
            return AnyCodable(dictionary[member])
        default:
            return nil
        }
    }
}

给定示例字典来自AnyCodable

let dictionary: [String: AnyEncodable] = [
    "boolean": true,
    "integer": 1,
    "double": 3.14159265358979323846,
    "string": "string",
    "array": [1, 2, 3],
    "nested": [
        "a": "alpha",
        "b": "bravo",
        "c": "charlie"
    ]
]

如果我做:

if let nested = dictionary["nested"] {
    print("nested a:", nested.a)
}

它输出:nested a: Optional(AnyCodable(Optional("alpha")))几乎就在那里!但我希望能够简单地写dictionary?.nested?.aORdictionary?.array?[1]而不是nested先用if let nested = dictionary["nested"]. 希望能够对其进行变异,例如:dictionary?.nested?.a? = "beta".

我不知道如何让它越过终点线。我显然需要添加case let array as [Any]:等,也许更改下标以包含 getter/setter?但我还缺少什么?

我知道您可能“不应该那样使用字典”并创建一个成熟的自定义类型模型等等,但这是一个小项目,走这条路会有点矫枉过正。所以请不要回答“以不同的方式建模你的数据”。我想将这两种现有的访问/持久化无类型字典或数组的方法合并为一个。

标签: swiftpersistencecodableswift4.2untyped-variables

解决方案


好吧,我想我大部分都涵盖了。

第一个问题是,您使用字典。您只能将@dynamicMemberLookup添加到主定义中,因此您不能在字典定义中执行此操作。尝试这个:

let dictionary: [String: AnyEncodable] = [ ... ]
let easierToUse = AnyCodable(dictionary)

所以考虑下面的代码,这是你需要的吗?:

let dictionary: [String: AnyCodable] = [
    "boolean": true,
    "integer": 1,
    "double": 3.14159265358979323846,
    "string": "string",
    "array": [1, 2, 3],
    "nested": [
        "a": "alpha",
        "b": "bravo",
        "c": "charlie",
        "array": [
            1,
            2,
            [
                "a": "alpha",
                "b": "bravo",
                "c": "deep charlie"
            ]
        ],
    ]
]
let easierToUse: AnyCodable = AnyCodable(dictionary)

if let value = easierToUse.nested?.a {
    print(value) // prints "alpha"
}

if let value = easierToUse.nested?.array?[2]?.c {
    print(value) // prints "deep charlie"
}

if let value = easierToUse.nested?.array?[2]?.c?.value as? String {
    print(value) // prints "deep charlie"
}

我不得不稍微更新您的课程,因为您忘记了它全部包含在每个级别:

// Helper to handle out of bounds on array with nil
extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

@dynamicMemberLookup
public struct AnyCodable: Codable {
    public let value: Any

    public init<T>(_ value: T) {
        self.value = value
    }

    public init<T>(_ value: T?) {
        self.value = value ?? ()
    }

    subscript(dynamicMember member: String) -> AnyCodable? {
        switch self.value {
        case let anyCodable as AnyCodable:
            return anyCodable[dynamicMember: member]
        case let dictionary as [String: Any?]:
            return AnyCodable(dictionary[member] ?? nil)
        default:
            return nil
        }
    }

    subscript(index: Int) -> AnyCodable? {
        switch self.value {
        case let anyCodable as AnyCodable:
            return anyCodable[index]
        case let array as [Any]:
            return AnyCodable(array[safe: index])
        default:
            return nil
        }
    }
}

推荐阅读