将结构保存到 UserDefaults
Save Struct to UserDefaults
我有一个要保存到 UserDefaults 的结构。这是我的结构
struct Song {
var title: String
var artist: String
}
var songs: [Song] = [
Song(title: "Title 1", artist "Artist 1"),
Song(title: "Title 2", artist "Artist 2"),
Song(title: "Title 3", artist "Artist 3"),
]
在另一个 ViewController 中,我有一个附加到此结构的 UIButton,例如
@IBAction func likeButtonPressed(_ sender: Any) {
songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))
}
我想要它,以便每当用户也单击该按钮时,它会将结构保存到 UserDefaults,这样每当用户退出应用程序然后再次打开它时,它就会被保存。我该怎么做?
来自here:
A default object must be a property list—that is, an instance of (or for collections, a combination of instances of):
NSData
,
NSString
,
NSNumber
,
NSDate
,
NSArray
, or
NSDictionary
. If you want to store any other type of object, you should typically archive it to create an instance of NSData.
您需要使用 NSKeydArchiver
。可以找到文档 here and examples here and here.
如果您只是想将这组歌曲保存在 UserDefaults 中,没有什么特别的,请使用:-
//stores the array to defaults
UserDefaults.standard.setValue(value: songs, forKey: "yourKey")
//retrieving the array
UserDefaults.standard.object(forKey: "yourKey") as! [Song]
//Make sure to typecast this as an array of Song
如果你存储的是一个很重的数组,我建议你使用 NSCoding 协议或者 swift 4
中的 Codable 协议
编码协议示例:-
struct Song {
var title: String
var artist: String
}
class customClass: NSObject, NSCoding { //conform to nsobject and nscoding
var songs: [Song] = [
Song(title: "Title 1", artist "Artist 1"),
Song(title: "Title 2", artist "Artist 2"),
Song(title: "Title 3", artist "Artist 3"),
]
override init(arr: [Song])
self.songs = arr
}
required convenience init(coder aDecoder: NSCoder) {
//decoding your array
let songs = aDecoder.decodeObject(forKey: "yourKey") as! [Song]
self.init(are: songs)
}
func encode(with aCoder: NSCoder) {
//encoding
aCoder.encode(songs, forKey: "yourKey")
}
}
如果结构仅包含 属性 列表兼容属性,我建议添加 属性 propertyListRepresentation
和相应的 init
方法
struct Song {
var title: String
var artist: String
init(title : String, artist : String) {
self.title = title
self.artist = artist
}
init?(dictionary : [String:String]) {
guard let title = dictionary["title"],
let artist = dictionary["artist"] else { return nil }
self.init(title: title, artist: artist)
}
var propertyListRepresentation : [String:String] {
return ["title" : title, "artist" : artist]
}
}
要将歌曲数组保存到 UserDefaults
写入
let propertylistSongs = songs.map{ [=11=].propertyListRepresentation }
UserDefaults.standard.set(propertylistSongs, forKey: "songs")
读取数组
if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] {
songs = propertylistSongs.flatMap{ Song(dictionary: [=12=]) }
}
如果 title
和 artist
永远不会发生突变,请考虑将属性声明为常量 (let
)。
此答案是在 Swift 4 处于测试状态时编写的。同时符合 Codable
是更好的解决方案。
在 Swift 4 中,这非常简单。只需将结构标记为采用 Codable 协议,即可使您的结构可编码:
struct Song:Codable {
var title: String
var artist: String
}
现在让我们从一些数据开始:
var songs: [Song] = [
Song(title: "Title 1", artist: "Artist 1"),
Song(title: "Title 2", artist: "Artist 2"),
Song(title: "Title 3", artist: "Artist 3"),
]
以下是将其放入 UserDefaults 的方法:
UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
下面是稍后将其取回的方法:
if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}
这是我在主线程UserDefaults 扩展,将get Codable 对象设置到UserDefaults
// MARK: - UserDefaults extensions
public extension UserDefaults {
/// Set Codable object into UserDefaults
///
/// - Parameters:
/// - object: Codable Object
/// - forKey: Key string
/// - Throws: UserDefaults Error
public func set<T: Codable>(object: T, forKey: String) throws {
let jsonData = try JSONEncoder().encode(object)
set(jsonData, forKey: forKey)
}
/// Get Codable object into UserDefaults
///
/// - Parameters:
/// - object: Codable Object
/// - forKey: Key string
/// - Throws: UserDefaults Error
public func get<T: Codable>(objectType: T.Type, forKey: String) throws -> T? {
guard let result = value(forKey: forKey) as? Data else {
return nil
}
return try JSONDecoder().decode(objectType, from: result)
}
}
Update 这是我的 UserDefaults 后台扩展 ,用于设置获取 Codable 对象用户默认值
// MARK: - JSONDecoder extensions
public extension JSONDecoder {
/// Decode an object, decoded from a JSON object.
///
/// - Parameter data: JSON object Data
/// - Returns: Decodable object
public func decode<T: Decodable>(from data: Data?) -> T? {
guard let data = data else {
return nil
}
return try? self.decode(T.self, from: data)
}
/// Decode an object in background thread, decoded from a JSON object.
///
/// - Parameters:
/// - data: JSON object Data
/// - onDecode: Decodable object
public func decodeInBackground<T: Decodable>(from data: Data?, onDecode: @escaping (T?) -> Void) {
DispatchQueue.global().async {
let decoded: T? = self.decode(from: data)
DispatchQueue.main.async {
onDecode(decoded)
}
}
}
}
// MARK: - JSONEncoder extensions
public extension JSONEncoder {
/// Encodable an object
///
/// - Parameter value: Encodable Object
/// - Returns: Data encode or nil
public func encode<T: Encodable>(from value: T?) -> Data? {
guard let value = value else {
return nil
}
return try? self.encode(value)
}
/// Encodable an object in background thread
///
/// - Parameters:
/// - encodableObject: Encodable Object
/// - onEncode: Data encode or nil
public func encodeInBackground<T: Encodable>(from encodableObject: T?, onEncode: @escaping (Data?) -> Void) {
DispatchQueue.global().async {
let encode = self.encode(from: encodableObject)
DispatchQueue.main.async {
onEncode(encode)
}
}
}
}
// MARK: - NSUserDefaults extensions
public extension UserDefaults {
/// Set Encodable object in UserDefaults
///
/// - Parameters:
/// - type: Encodable object type
/// - key: UserDefaults key
/// - Throws: An error if any value throws an error during encoding.
public func set<T: Encodable>(object type: T, for key: String, onEncode: @escaping (Bool) -> Void) throws {
JSONEncoder().encodeInBackground(from: type) { [weak self] (data) in
guard let data = data, let `self` = self else {
onEncode(false)
return
}
self.set(data, forKey: key)
onEncode(true)
}
}
/// Get Decodable object in UserDefaults
///
/// - Parameters:
/// - objectType: Decodable object type
/// - forKey: UserDefaults key
/// - onDecode: Codable object
public func get<T: Decodable>(object type: T.Type, for key: String, onDecode: @escaping (T?) -> Void) {
let data = value(forKey: key) as? Data
JSONDecoder().decodeInBackground(from: data, onDecode: onDecode)
}
}
这是一个现代 Swift 5.1 @propertyWrapper
,允许以人类可读 JSON 字符串的形式存储任何 Codable
对象:
@propertyWrapper struct UserDefaultEncoded<T: Codable> {
let key: String
let defaultValue: T
init(key: String, default: T) {
self.key = key
defaultValue = `default`
}
var wrappedValue: T {
get {
guard let jsonString = UserDefaults.standard.string(forKey: key) else {
return defaultValue
}
guard let jsonData = jsonString.data(using: .utf8) else {
return defaultValue
}
guard let value = try? JSONDecoder().decode(T.self, from: jsonData) else {
return defaultValue
}
return value
}
set {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
guard let jsonData = try? encoder.encode(newValue) else { return }
let jsonString = String(bytes: jsonData, encoding: .utf8)
UserDefaults.standard.set(jsonString, forKey: key)
}
}
}
用法:
extension Song: Codable {}
@UserDefaultEncoded(key: "songs", default: [])
var songs: [Song]
func addSong(_ song: Song) {
// This will automatically store new `songs` value
// to UserDefaults
songs.append(song)
}
我认为将用户设置表示为可观察对象应该很常见。因此,这是一个保持可观察数据与用户默认值同步并针对 xCode 11.4 进行更新的示例。这也可以在环境对象的上下文中使用。
import SwiftUI
final class UserData: ObservableObject {
@Published var selectedAddress: String? {
willSet {
UserDefaults.standard.set(newValue, forKey: Keys.selectedAddressKey)
}
}
init() {
selectedAddress = UserDefaults.standard.string(forKey: Keys.selectedAddressKey)
}
private struct Keys {
static let selectedAddressKey = "SelectedAddress"
}
}
Swift 5
如果您需要将 struct 保存在 UserDefault 中,仅使用 data 格式。
Smaple 结构
struct StudentData:Codable{
var id: Int?
var name: String?
var createdDate: String?
// for decode the value
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: codingKeys.self)
id = try? values?.decodeIfPresent(Int.self, forKey: .id)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
createdDate = try? values?.decodeIfPresent(String.self, forKey: .createdDate)
}
// for encode the value
func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: codingKeys.self)
try? values.encodeIfPresent(id, forKey: .id)
try? values.encodeIfPresent(name, forKey: .name)
try? values.encodeIfPresent(createdDate, forKey: .createdDate)
}
}
有两种类型可以转换为数据
- 可编码(可编码和可解码)。
- PropertyListEncoder 和 PropertyListDecoder
首先我们使用Codable(Encodable and Decodable)来保存struct
保存值示例
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z")
guard let data = try? JSONEncoder().encode(value) else {
fatalError("unable encode as data")
}
UserDefaults.standard.set(data, forKey: "Top_student_record")
检索值
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else {
// write your code as per your requirement
return
}
guard let value = try? JSONDecoder().decode(StudentData.self, from: data) else {
fatalError("unable to decode this data")
}
print(value)
现在我们使用 PropertyListEncoder 和 PropertyListDecoder 来保存结构
保存值示例
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z")
guard let data = try? PropertyListEncoder().encode(value) else {
fatalError("unable encode as data")
}
UserDefaults.standard.set(data, forKey: "Top_student_record")
检索值
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else {
// write your code as per your requirement
return
}
guard let value = try? PropertyListDecoder().decode(StudentData.self, from: data) else {
fatalError("unable to decode this data")
}
print(value)
为方便起见,您可以使用 any 类型将结构保存在 userDefault 中。
这是一个更简单的解决方案
@propertyWrapper
struct CodableUserDefault<Value: Codable> {
let key: String
let defaultValue: Value
private let container: UserDefaults = .standard
var wrappedValue: Value {
get {
guard let data = container.data(forKey: key), let object = try? JSONDecoder().decode(Value.self, from: data) else {
return defaultValue
}
return object
}
set {
container.set(try? JSONEncoder().encode(newValue), forKey: key)
}
}
}
用法
enum ACodableEnum: String, Codable {
case first
case second
}
class SomeController {
@CodableUserDefault<ACodableEnum>(key: "key", defaultValue: .first)
private var aCodableEnum: ACodableEnum
}
我有一个要保存到 UserDefaults 的结构。这是我的结构
struct Song {
var title: String
var artist: String
}
var songs: [Song] = [
Song(title: "Title 1", artist "Artist 1"),
Song(title: "Title 2", artist "Artist 2"),
Song(title: "Title 3", artist "Artist 3"),
]
在另一个 ViewController 中,我有一个附加到此结构的 UIButton,例如
@IBAction func likeButtonPressed(_ sender: Any) {
songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))
}
我想要它,以便每当用户也单击该按钮时,它会将结构保存到 UserDefaults,这样每当用户退出应用程序然后再次打开它时,它就会被保存。我该怎么做?
来自here:
A default object must be a property list—that is, an instance of (or for collections, a combination of instances of): NSData , NSString , NSNumber , NSDate , NSArray , or NSDictionary . If you want to store any other type of object, you should typically archive it to create an instance of NSData.
您需要使用 NSKeydArchiver
。可以找到文档 here and examples here and here.
如果您只是想将这组歌曲保存在 UserDefaults 中,没有什么特别的,请使用:-
//stores the array to defaults
UserDefaults.standard.setValue(value: songs, forKey: "yourKey")
//retrieving the array
UserDefaults.standard.object(forKey: "yourKey") as! [Song]
//Make sure to typecast this as an array of Song
如果你存储的是一个很重的数组,我建议你使用 NSCoding 协议或者 swift 4
中的 Codable 协议编码协议示例:-
struct Song {
var title: String
var artist: String
}
class customClass: NSObject, NSCoding { //conform to nsobject and nscoding
var songs: [Song] = [
Song(title: "Title 1", artist "Artist 1"),
Song(title: "Title 2", artist "Artist 2"),
Song(title: "Title 3", artist "Artist 3"),
]
override init(arr: [Song])
self.songs = arr
}
required convenience init(coder aDecoder: NSCoder) {
//decoding your array
let songs = aDecoder.decodeObject(forKey: "yourKey") as! [Song]
self.init(are: songs)
}
func encode(with aCoder: NSCoder) {
//encoding
aCoder.encode(songs, forKey: "yourKey")
}
}
如果结构仅包含 属性 列表兼容属性,我建议添加 属性 propertyListRepresentation
和相应的 init
方法
struct Song {
var title: String
var artist: String
init(title : String, artist : String) {
self.title = title
self.artist = artist
}
init?(dictionary : [String:String]) {
guard let title = dictionary["title"],
let artist = dictionary["artist"] else { return nil }
self.init(title: title, artist: artist)
}
var propertyListRepresentation : [String:String] {
return ["title" : title, "artist" : artist]
}
}
要将歌曲数组保存到 UserDefaults
写入
let propertylistSongs = songs.map{ [=11=].propertyListRepresentation }
UserDefaults.standard.set(propertylistSongs, forKey: "songs")
读取数组
if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] {
songs = propertylistSongs.flatMap{ Song(dictionary: [=12=]) }
}
如果 title
和 artist
永远不会发生突变,请考虑将属性声明为常量 (let
)。
此答案是在 Swift 4 处于测试状态时编写的。同时符合 Codable
是更好的解决方案。
在 Swift 4 中,这非常简单。只需将结构标记为采用 Codable 协议,即可使您的结构可编码:
struct Song:Codable {
var title: String
var artist: String
}
现在让我们从一些数据开始:
var songs: [Song] = [
Song(title: "Title 1", artist: "Artist 1"),
Song(title: "Title 2", artist: "Artist 2"),
Song(title: "Title 3", artist: "Artist 3"),
]
以下是将其放入 UserDefaults 的方法:
UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
下面是稍后将其取回的方法:
if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}
这是我在主线程UserDefaults 扩展,将get Codable 对象设置到UserDefaults
// MARK: - UserDefaults extensions
public extension UserDefaults {
/// Set Codable object into UserDefaults
///
/// - Parameters:
/// - object: Codable Object
/// - forKey: Key string
/// - Throws: UserDefaults Error
public func set<T: Codable>(object: T, forKey: String) throws {
let jsonData = try JSONEncoder().encode(object)
set(jsonData, forKey: forKey)
}
/// Get Codable object into UserDefaults
///
/// - Parameters:
/// - object: Codable Object
/// - forKey: Key string
/// - Throws: UserDefaults Error
public func get<T: Codable>(objectType: T.Type, forKey: String) throws -> T? {
guard let result = value(forKey: forKey) as? Data else {
return nil
}
return try JSONDecoder().decode(objectType, from: result)
}
}
Update 这是我的 UserDefaults 后台扩展 ,用于设置获取 Codable 对象用户默认值
// MARK: - JSONDecoder extensions
public extension JSONDecoder {
/// Decode an object, decoded from a JSON object.
///
/// - Parameter data: JSON object Data
/// - Returns: Decodable object
public func decode<T: Decodable>(from data: Data?) -> T? {
guard let data = data else {
return nil
}
return try? self.decode(T.self, from: data)
}
/// Decode an object in background thread, decoded from a JSON object.
///
/// - Parameters:
/// - data: JSON object Data
/// - onDecode: Decodable object
public func decodeInBackground<T: Decodable>(from data: Data?, onDecode: @escaping (T?) -> Void) {
DispatchQueue.global().async {
let decoded: T? = self.decode(from: data)
DispatchQueue.main.async {
onDecode(decoded)
}
}
}
}
// MARK: - JSONEncoder extensions
public extension JSONEncoder {
/// Encodable an object
///
/// - Parameter value: Encodable Object
/// - Returns: Data encode or nil
public func encode<T: Encodable>(from value: T?) -> Data? {
guard let value = value else {
return nil
}
return try? self.encode(value)
}
/// Encodable an object in background thread
///
/// - Parameters:
/// - encodableObject: Encodable Object
/// - onEncode: Data encode or nil
public func encodeInBackground<T: Encodable>(from encodableObject: T?, onEncode: @escaping (Data?) -> Void) {
DispatchQueue.global().async {
let encode = self.encode(from: encodableObject)
DispatchQueue.main.async {
onEncode(encode)
}
}
}
}
// MARK: - NSUserDefaults extensions
public extension UserDefaults {
/// Set Encodable object in UserDefaults
///
/// - Parameters:
/// - type: Encodable object type
/// - key: UserDefaults key
/// - Throws: An error if any value throws an error during encoding.
public func set<T: Encodable>(object type: T, for key: String, onEncode: @escaping (Bool) -> Void) throws {
JSONEncoder().encodeInBackground(from: type) { [weak self] (data) in
guard let data = data, let `self` = self else {
onEncode(false)
return
}
self.set(data, forKey: key)
onEncode(true)
}
}
/// Get Decodable object in UserDefaults
///
/// - Parameters:
/// - objectType: Decodable object type
/// - forKey: UserDefaults key
/// - onDecode: Codable object
public func get<T: Decodable>(object type: T.Type, for key: String, onDecode: @escaping (T?) -> Void) {
let data = value(forKey: key) as? Data
JSONDecoder().decodeInBackground(from: data, onDecode: onDecode)
}
}
这是一个现代 Swift 5.1 @propertyWrapper
,允许以人类可读 JSON 字符串的形式存储任何 Codable
对象:
@propertyWrapper struct UserDefaultEncoded<T: Codable> {
let key: String
let defaultValue: T
init(key: String, default: T) {
self.key = key
defaultValue = `default`
}
var wrappedValue: T {
get {
guard let jsonString = UserDefaults.standard.string(forKey: key) else {
return defaultValue
}
guard let jsonData = jsonString.data(using: .utf8) else {
return defaultValue
}
guard let value = try? JSONDecoder().decode(T.self, from: jsonData) else {
return defaultValue
}
return value
}
set {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
guard let jsonData = try? encoder.encode(newValue) else { return }
let jsonString = String(bytes: jsonData, encoding: .utf8)
UserDefaults.standard.set(jsonString, forKey: key)
}
}
}
用法:
extension Song: Codable {}
@UserDefaultEncoded(key: "songs", default: [])
var songs: [Song]
func addSong(_ song: Song) {
// This will automatically store new `songs` value
// to UserDefaults
songs.append(song)
}
我认为将用户设置表示为可观察对象应该很常见。因此,这是一个保持可观察数据与用户默认值同步并针对 xCode 11.4 进行更新的示例。这也可以在环境对象的上下文中使用。
import SwiftUI
final class UserData: ObservableObject {
@Published var selectedAddress: String? {
willSet {
UserDefaults.standard.set(newValue, forKey: Keys.selectedAddressKey)
}
}
init() {
selectedAddress = UserDefaults.standard.string(forKey: Keys.selectedAddressKey)
}
private struct Keys {
static let selectedAddressKey = "SelectedAddress"
}
}
Swift 5
如果您需要将 struct 保存在 UserDefault 中,仅使用 data 格式。
Smaple 结构
struct StudentData:Codable{
var id: Int?
var name: String?
var createdDate: String?
// for decode the value
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: codingKeys.self)
id = try? values?.decodeIfPresent(Int.self, forKey: .id)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
createdDate = try? values?.decodeIfPresent(String.self, forKey: .createdDate)
}
// for encode the value
func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: codingKeys.self)
try? values.encodeIfPresent(id, forKey: .id)
try? values.encodeIfPresent(name, forKey: .name)
try? values.encodeIfPresent(createdDate, forKey: .createdDate)
}
}
有两种类型可以转换为数据
- 可编码(可编码和可解码)。
- PropertyListEncoder 和 PropertyListDecoder
首先我们使用Codable(Encodable and Decodable)来保存struct
保存值示例
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z")
guard let data = try? JSONEncoder().encode(value) else {
fatalError("unable encode as data")
}
UserDefaults.standard.set(data, forKey: "Top_student_record")
检索值
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else {
// write your code as per your requirement
return
}
guard let value = try? JSONDecoder().decode(StudentData.self, from: data) else {
fatalError("unable to decode this data")
}
print(value)
现在我们使用 PropertyListEncoder 和 PropertyListDecoder 来保存结构
保存值示例
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z")
guard let data = try? PropertyListEncoder().encode(value) else {
fatalError("unable encode as data")
}
UserDefaults.standard.set(data, forKey: "Top_student_record")
检索值
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else {
// write your code as per your requirement
return
}
guard let value = try? PropertyListDecoder().decode(StudentData.self, from: data) else {
fatalError("unable to decode this data")
}
print(value)
为方便起见,您可以使用 any 类型将结构保存在 userDefault 中。
这是一个更简单的解决方案
@propertyWrapper
struct CodableUserDefault<Value: Codable> {
let key: String
let defaultValue: Value
private let container: UserDefaults = .standard
var wrappedValue: Value {
get {
guard let data = container.data(forKey: key), let object = try? JSONDecoder().decode(Value.self, from: data) else {
return defaultValue
}
return object
}
set {
container.set(try? JSONEncoder().encode(newValue), forKey: key)
}
}
}
用法
enum ACodableEnum: String, Codable {
case first
case second
}
class SomeController {
@CodableUserDefault<ACodableEnum>(key: "key", defaultValue: .first)
private var aCodableEnum: ACodableEnum
}