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

Posted

技术标签:

【中文标题】确定可以存储在 UserDefaults 中的 Swift 类型【英文标题】:Determining Swift Types That Can Be Stored in UserDefaults 【发布时间】:2019-08-15 15:18:19 【问题描述】:

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

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

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

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

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

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

根据the UserDefaults docs,有一组特定的类型和类是“plist-studly”。

我认为测试列表中的每种类型是不可接受的。我需要看看我是否能找到一种不会在 Apple 第一次更新操作系统时搞砸的测试方法。

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

【问题讨论】:

[String: Any] 从不符合 Codable 因为 Any 不符合所以你不需要测试任何东西。也许您应该将数据存储在结构或类中而不是字典中 问题是它不需要是可编码的。 [String: Any] 可以很容易地成为一个 NSDictionary,这很好。我使用这种通用类型是有原因的。我正在制作一个相当通用的基类,它允许子类在值 Dictionary 中存储他们想要的任何内容。 所以我想做的就是在字典发送之前检查它,这样我就可以在不导致程序崩溃的情况下找到并报告问题。 我可以让它崩溃,因为这不太可能是运行时问题,但我能为我的工具用户提供的帮助越多越好。 “如果这是 ObjC,我可能会使用 NSPropertyListSerialization 方法之一来测试 Dictionary,但看起来这组方法对 Swift 不可用。”你能进一步解释一下吗?在 看来,这些方法不可用。 【参考方案1】:

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

NSString → 斯威夫特String NSNumber → 斯威夫特 IntDoubleBool NSDate → 斯威夫特Date NSData → 斯威夫特Data 包含 4 种值类型的数组和字典。

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

可以使用PropertyListSerialization 将符合属性列表的集合类型写入UserDefaults(即使在Swift 中也是如此)。


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

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

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


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

【讨论】:

我会检查一下。谢谢!【参考方案2】:

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[$0] = _values[$0]
    
    _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($0.value, isValidFor: .xml) 
                #if DEBUG
                    print("\($0.key) is OK")
                #endif
             else 
                #if DEBUG
                    print("\($0.key) is not Codable")
                #endif
                valueElementList.append($0.key)
            
        
        throw PrefsError.valuesNotCodable(invalidElements: valueElementList)
    

【讨论】:

我不会做递归兔子洞的事情。我只会让***键告诉实现者在哪里寻找问题。这似乎工作得很好。

以上是关于确定可以存储在 UserDefaults 中的 Swift 类型的主要内容,如果未能解决你的问题,请参考以下文章

检测存储在 UserDefaults 中的数组中的项目

将 IAP 存储在 UserDefaults 中,这样用户就不需要在每次按下“可购买”按钮时恢复购买

将 Decodable 结构存储到 Userdefaults

您是不是能够跨不同的视图控制器/.swift 文件检索存储在 UserDefaults 中的数据?

只有 NSDictionary 中的属性列表对象(NSNumber),但不能在 UserDefaults 中存储 dict

在 UserDefaults 中存储 OpaquePointer 类型