Swift 实现将自定义对象保存到 UserDefaults 中

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战


引言

大家都知道,iOS 支持这些类型的对象,直接保存到 UserDefaults 中,比如 Int,String,Float,Double,Bool,URL,Data 或者这些类型的集合。

但是如果我们想要将自定义对象保存到 UserDefaults 中呢?

看看苹果的说法:

If you want to store any other type of object, you should typically archive it to create an instance of NSData

如果要存储任何其他类型的对象,通常应该对其进行归档,来创建一个 NSData 实例。

Apple Documentation

现在我们知道要保存自定义对象到 UserDefaults 中,就要把它转换为 Data 对象。基于此,我们可以使用 Codable 来做进一步的完善。


声明

先定义一个协议 ObjectSavable,声明了两个方法,一个保存对象,另一个查询对象。

protocol ObjectSavable {
    func setObject<Object>(_ object: Object, forKey: String) throws where Object: Encodable
    func getObject<Object>(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable
}
复制代码

setObject 方法接受一个类型符合 Encodable 协议的对象和一个想要与之关联的键;

getObject 方法接受一个键,通过该键我们将从 UserDefaults 查询关联的对象,并接受一个符合 Decodable 协议的类型。为了传递类型本身,我们使用了元类型

metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.

元类型指的是任何类型的类型,包括类类型、结构体类型、枚举类型和协议类型。

Apple Documentation

实现

ObjectSavable 协议已经声明好了,通过给 UserDefaults 添加扩展,遵循 ObjectSavable 协议,并提供具体的实现:

extension UserDefaults: ObjectSavable {

    func setObject<Object>(_ object: Object, forKey: String) throws where Object: Encodable {
        let encoder = JSONEncoder()
        do {
            let data = try encoder.encode(object)
            set(data, forKey: forKey)
        } catch {
            throw ObjectSavableError.unableToEncode
        }
    }

    func getObject<Object>(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable {
        guard let data = data(forKey: forKey) else { throw ObjectSavableError.noValue }
        let decoder = JSONDecoder()
        do {
            let object = try decoder.decode(type, from: data)
            return object
        } catch {
            throw ObjectSavableError.unableToDecode
        }
    }

}
复制代码

看下实现里都做了什么:

setObject

先创建了一个 JSONEncoder 实例,使用该实例将自定义对象转换为 Data 对象。然后把它保存到 UserDefaults 中。

getObject

先使用 guard-let 保证是能取到数据的。然后创建一个 JSONDecoder 对象,最后,使用该实例将这个 Data 对象解码为给定的类型并返回它。

注意 如果上面的任何一个方法在任何一个步骤上失败了,它就会抛出一个相关的错误,提示开发人员可能出了什么问题。

为了方便管理这些错误,定义一个字符串枚举 ObjectSavableError 并遵循 LocalizedError 协议:

enum ObjectSavableError: String, LocalizedError {
    case unableToEncode = "Unable to encode object into data"
    case noValue = "No data object found for the given key"
    case unableToDecode = "Unable to decode object into given type"

    var errorDescription: String? {
        rawValue
    }
}
复制代码

举个例子

所有实现已经完成, 来个例子看看看效果:

先定义一个 Book 结构体:

struct Book: Codable {
    var title: String
    var authorName: String
    var pageCount: Int
}
复制代码

存储

由于此方法在失败时会抛出错误,因此我们使用 do-try-catch 语句来捕捉错误

let userDefaults = UserDefaults.standard

let playingItMyWay = Book(title: "《微信背后的产品观》", authorName: "龙哥", pageCount: 888)
do {
    try userDefaults.setObject(playingItMyWay, forKey: "WeChat")
} catch {
    print(error.localizedDescription)
}
复制代码

查询

print(playingItMyWay) 语句将输出:
Book(title: "《微信背后的产品观》", authorName: "龙哥", pageCount: 888)

let userDefaults = UserDefaults.standard

do {
    let playingItMyWay = try userDefaults.getObject(forKey: "WeChat", castTo: Book.self)
    print(playingItMyWay) 
} catch {
    print(error.localizedDescription)
}
复制代码

总结

如果觉得对你有帮助,不妨在项目中试试吧~

猜你喜欢

转载自juejin.im/post/7036240681026715656