UserDefaults存储Set类型数据
UserDefaults存储Set类型数据

UserDefaults存储Set类型数据

问题描述

在练习代码时,发现UserDefaults可以存储Set类型的数据,但是UserDefaults没有合适的方法来读取Set类型的数据:

class Favorites {
    private var resorts: Set<String>

    private let key = "Favorites"

    init() {
        resorts = UserDefaults.standard.array(forKey: key)  // 报错
        resorts = []
    }

    func save() {
        UserDefaults.standard.set(resorts, forKey: key)
    }
}

报错内容为

Cannot assign value of type '[Any]?' to type 'Set<String>'

表示无法将类型“[Any]?”的值分配给类型“Set<String>”。

问题原因

UserDefaults 支持的基础数据类型包括 Array、Dictionary、String、Number(Int、Double 等)和 Data,但是它不直接支持 Set。因此,当试图使用 UserDefaults.standard.array(forKey:) 加载一个 Set<String> 类型时,会发生类型不匹配。

解决方案

为了正确加载和保存 Set<String>,可以先将 Set 转换为 Array,然后在需要时再转换回 Set。

class Favorites {
    private var resorts: Set<String>

    private let key = "Favorites"

    init() {
        if let resortsSave = UserDefaults.standard.array(forKey: key) as? [String] {
            resorts = Set(resortsSave) // 将 Array 转换为 Set
            print("初始化成功")
        } else {
            // 如果没有数据,使用空的集合
            resorts = []
        }
    }

    func save() {
        UserDefaults.standard.set(Array(resorts), forKey: key) // 将 Set 转换为 Array
    }
}

关键改动说明

          1、从 UserDefaults 加载数据时

          使用 UserDefaults.standard.array(forKey:) 加载 Array<String> 类型。

          将 Array 转换为 Set:Set(resortsSave)。

          2、保存数据到 UserDefaults 时

          将 Set 转换为 Array:Array(resorts)。

为什么这样解决?

Set 是一个无序集合,而 UserDefaults 支持的 Array 是有序的列表。通过在加载和保存时在 Set 和 Array 之间转换,可以保持数据的兼容性,同时利用 Set 的高效集合操作。

为什么使用as? [String]

在 Swift 中,UserDefaults 是一个通用的存储系统,但它并不会强制要求存储的数据类型与读取时完全一致。因此,as? [String] 用于安全地将从 UserDefaults 中读取的数据尝试转换为 Array<String> 类型。

原因解析

1、UserDefaults.standard.array(forKey:) 的返回值类型

array(forKey:) 返回的是一个 Any? 类型。

这意味着它可能是任何类型(如果有值)或者 nil(如果没有找到对应的键值)。

2、需要进行类型匹配

期望读取的值是 [String] 类型(一个字符串数组)。

但由于 UserDefaults 是非类型化的存储,因此必须使用类型转换。

使用 as? [String] 可以安全地尝试将返回值转换为 [String],如果转换失败,则返回 nil。

3、为什么不是直接用 as! 强制转换?

强制转换(as!)在类型不匹配时会引发运行时崩溃。

而 as? 是一种安全的方式,它会在类型不匹配时返回 nil,避免崩溃。

扩展知识

为什么 UserDefaults.standard.array(forKey:) 返回的是 Any? 类型?

UserDefaults 是一种通用存储系统,用来存储各种数据类型(如 String、Int、Bool、Array 等),但在底层,它并不直接绑定某一种特定的类型,而是使用了 Any。因此:

当调用 array(forKey:) 时,它会尝试读取与指定键相关联的值,但它的返回值是一个泛型的 Any?,表示“可能是任何类型或 nil”。

需要对它进行类型转换,以确保拿到的值符合期望的类型(在这个例子中是 [Any] 或 [String])。

func array(forKey defaultName: String) -> [Any]? // 返回的是 [Any]? 类型

设计原因:array(forKey:) 的设计假定用户可能存储的是任意数组类型,因此返回 Any,具体的类型需要用户手动确认。

为什么 UserDefaults.standard.bool(forKey:) 返回的不是 Any??

这是因为 bool(forKey:) 和其他特定方法(如 integer(forKey:))是专门为某些基本数据类型设计的便捷方法。这些方法直接返回预期类型的数据值,并提供默认值。

具体方法签名

func bool(forKey defaultName: String) -> Bool // 直接返回 Bool 值

重要特点

如果键存在且对应的值是 Bool,就返回该值。

如果键不存在,返回 false(默认值)。

不需要 as? 或类型转换,因为返回值已经是明确的 Bool。

设计原因:布尔值是非常常见的类型,因此专门提供了便捷方法,简化开发者操作。

为什么 array(forKey:) 和 bool(forKey:) 的行为不同?

核心原因:两者的适用场景不同

array(forKey:) 的目标

支持通用数组存储,允许存储任何类型的数组(如 [String]、[Int])。因此,它返回的是 Any?,需要手动确认类型。

bool(forKey:) 的目标

专门设计用于布尔值,只有一种可能的数据类型,因此直接返回 Bool,无需额外的类型检查。

示例对比

array(forKey:)
if let array = UserDefaults.standard.array(forKey: "myKey") as? [String] {
    // 成功转换为 [String]
    print(array)
} else {
    // 键不存在,或者类型不匹配
    print("没有找到字符串数组")
}

因为返回值是 Any?,所以需要使用 as? [String] 明确类型。

bool(forKey:)
let isOn = UserDefaults.standard.bool(forKey: "isSwitchOn")
print(isOn) // true 或 false(默认值)

不需要类型转换,因为返回值已经明确为 Bool。

如果键不存在,它会返回默认值 false。

为什么 bool(forKey:) 不需要类型检查,而 array(forKey:) 需要?

这归因于 底层数据存储的差异

1、布尔值

布尔值是一种非常基础的数据类型,并且 UserDefaults 内部可以轻松检测和处理是否是 Bool 类型。

如果不是布尔值,它会返回默认值而不是引发崩溃。

2、数组

数组本身可能包含不同的数据类型(如 [Int]、[String]、[Any]),UserDefaults 无法明确知道存储的数组具体元素的类型。因此,它只能以最通用的 Any 类型返回。

相关文章

1、Swift UserDefaults.standard返回类型https://fangjunyu.com/2024/12/24/swift-userdefaults-standard%e8%bf%94%e5%9b%9e%e7%b1%bb%e5%9e%8b/

2、Swift使用UserDefaults保存数据https://fangjunyu.com/2024/05/26/swift%e4%bd%bf%e7%94%a8userdefaults%e4%bf%9d%e5%ad%98%e6%95%b0%e6%8d%ae/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注