确定可以存储在 UserDefaults 中的 Swift 类型

Determining Swift Types That Can Be Stored in UserDefaults

我正处于发展的初级阶段 an open-source utility for storing state in the Bundle UserDefaults

我在向 [String: Any]Dictionary 添加非 Codable 数据类型时遇到问题。

我需要能够在尝试提交之前检查数据,因为 UserDefaults.set(_:) 方法不会抛出任何错误。它只是崩溃。

所以我想确保我提交的 Dictionary 是犹太洁食。

我不能只检查它是否是 Codable,因为它有时会说它不是,而实际上结构是好的。 (就是一个Dictionary<String, Any>,我可以把各种东西都塞进去)

我需要验证 Dictionary 是否可以生成 plist。如果这是 ObjC,我可能会使用 NSPropertyListSerialization 方法之一来测试 Dictionary,但似乎这组方法不适用于 Swift。

根据 the UserDefaults docs,有一组特定类型和 类 是 "plist-studly."

我认为测试列表中的每种类型是不可接受的。我需要看看我是否能找到一种方法来测试它不会在 Apple 第一次更新 OS.

时被搞砸

有什么好的方法可以测试 Dictionary<String, Any> 是否会让 UserDefaults.set(_:) 呕吐?

UserDefaults的属性列表类型集非常有限。支持的类型有

  • NSString→SwiftString
  • NSNumber → Swift IntDoubleBool
  • NSDate→SwiftDate
  • NSData→SwiftData
  • 包含 4 种值类型的数组和字典。

Any 不受支持,除非它表示 4 种值或 2 种集合类型之一。

属性 列表兼容的集合类型可以用 PropertyListSerialization 写入 UserDefaults(即使在 Swift 中)。


有两种协议可以将自定义类型序列化为 Data

  • Codable 可以序列化结构和 类.
  • NSCoding 可以序列化 NSObject 的子 类。

structs/classes 中的所有类型必须是可编码和可解码的(意味着符合协议本身)。


PropertyListSerialization / PropertyListEncoder/-DecoderNSKeyed(Un)Archiver 的 API 提供强大的错误处理功能以避免崩溃。

UPDATE[1]: 而且,正因为我喜欢分享,here's the actual completed project (MIT License, as is most of my stuff)

更新: This is the solution I came up with。尽管我对 vadian 的出色回答进行了绿色检查,但我还是决定挑剔一点。

感谢 matt 指出我在错误的沙发垫下寻找钥匙,我找到了 NSPropertyListSerialization 的 Swift 变体,我用它来检查树。我怀疑我需要在完成之前将其重构为递归爬虫,但这暂时有效。

这是撰写本文时 _save() 方法的代码。有效:

/* ################################################################## */
/**
 This is a private method that saves the current contents of the _values Dictionary to persistent storage, keyed by the value of the "key" property.

 - throws: An error, if the values are not all codable.
 */
private func _save() throws {
    #if DEBUG
        print("Saving Prefs: \(String(describing: _values))")
    #endif

    // What we do here, is "scrub" the values of anything that was added against what is expected.
    var temporaryDict: [String: Any] = [:]
    keys.forEach {
        temporaryDict[[=10=]] = _values[[=10=]]
    }
    _values = temporaryDict

    if PropertyListSerialization.propertyList(_values, isValidFor: .xml) {
        UserDefaults.standard.set(_values, forKey: key)
    } else {
        #if DEBUG
            print("Attempt to set non-codable values!")
        #endif

        // What we do here, is look through our values list, and record the keys of the elements that are not considered Codable. We return those in the error that we throw.
        var valueElementList: [String] = []
        _values.forEach {
            if PropertyListSerialization.propertyList([=10=].value, isValidFor: .xml) {
                #if DEBUG
                    print("\([=10=].key) is OK")
                #endif
            } else {
                #if DEBUG
                    print("\([=10=].key) is not Codable")
                #endif
                valueElementList.append([=10=].key)
            }
        }
        throw PrefsError.valuesNotCodable(invalidElements: valueElementList)
    }
}