ios 将结构保存到用户默认值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/44876420/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Save Struct to UserDefaults
提问by Jacob Cavin
I have a struct that I want to save to UserDefaults. Here's my struct
我有一个要保存到 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"),
]
In another ViewController, I have a UIButton that appends to this struct like
在另一个 ViewController 中,我有一个 UIButton 附加到这个结构,如
@IBAction func likeButtonPressed(_ sender: Any) {
songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))
}
I want it so that whenever the user clicks on that button also, it saves the struct to UserDefaults so that whenever the user quits the app and then opens it agian, it is saved. How would I do this?
我想要它,以便每当用户单击该按钮时,它也会将结构保存到 UserDefaults,以便每当用户退出应用程序然后再次打开它时,它就会被保存。我该怎么做?
回答by matt
In Swift 4 this is pretty much trivial. Make your struct codable simply by marking it as adopting the Codable protocol:
在 Swift 4 中,这几乎是微不足道的。只需将结构标记为采用 Codable 协议即可使您的结构可编码:
struct Song:Codable {
var title: String
var artist: String
}
Now let's start with some data:
现在让我们从一些数据开始:
var songs: [Song] = [
Song(title: "Title 1", artist: "Artist 1"),
Song(title: "Title 2", artist: "Artist 2"),
Song(title: "Title 3", artist: "Artist 3"),
]
Here's how to get that into UserDefaults:
以下是将其放入 UserDefaults 的方法:
UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
And here's how to get it back out again later:
以下是稍后再次将其取回的方法:
if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}
回答by YannSteph
This is my UserDefaults extension in main thread, to set get Codableobject into UserDefaults
这是我在主线程中的 UserDefaults 扩展,将获取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)
}
}
UpdateThis is my UserDefaults extension in background, to set get Codableobject into UserDefaults
更新这是我在后台的 UserDefaults 扩展,将获取Codable对象设置为 UserDefaults
// 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)
}
}
回答by vadian
If the struct contains only property list compliant properties I recommend to add a property propertyListRepresentation
and a corresponding init
method
如果结构仅包含符合属性列表的属性,我建议添加属性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]
}
}
To save an array of songs to UserDefaults
write
保存要UserDefaults
写入的歌曲数组
let propertylistSongs = songs.map{ if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] {
songs = propertylistSongs.flatMap{ Song(dictionary: @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)
}
}
}
) }
}
.propertyListRepresentation }
UserDefaults.standard.set(propertylistSongs, forKey: "songs")
To read the array
读取数组
extension Song: Codable {}
@UserDefaultEncoded(key: "songs", default: [])
var songs: [Song]
If title
and artist
will never be mutated consider to declare the properties as constants (let
) .
如果title
并且artist
永远不会发生变异,请考虑将属性声明为常量 ( let
)。
This answer was written while Swift 4 was in beta status. Meanwhile conforming to Codable
is the better solution.
这个答案是在 Swift 4 处于测试状态时写的。同时符合Codable
是更好的解决方案。
回答by kelin
Here is a modern Swift 5.1 @propertyWrapper
, allowing to store any Codable
object in form of a human readable JSON string:
这是一个现代的 Swift 5.1 @propertyWrapper
,允许以Codable
人类可读的 JSON 字符串的形式存储任何对象:
//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
Usage:
用法:
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")
}
}
回答by Valdmer
From 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.
默认对象必须是一个属性列表——也就是说,一个实例(或对于集合,实例的组合): NSData 、 NSString 、 NSNumber 、 NSDate 、 NSArray 或 NSDictionary 。如果要存储任何其他类型的对象,通常应该将其存档以创建 NSData 的实例。
You need to use NSKeydArchiver
. Documentation can be found hereand examples hereand here.
回答by Dark Innocence
If you are just trying to save this array of songs in UserDefaults and nothing fancy use this:-
如果您只是想将这一系列歌曲保存在 UserDefaults 中并且没什么特别的,请使用:-
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"
}
}
If you are storing a heavy array, I suggest you to go with NSCoding protocol or the Codable Protocol in swift 4
如果您要存储大量数组,我建议您使用 swift 4 中的 NSCoding 协议或 Codable 协议
Example of coding protocol:-
编码协议示例:-
##代码##回答by Christopher Hunt
I'd imagine that it should be quite common to represent a user's settings as an observable object. So, here's an example of keeping observable data synchronised with user defaults and updated for xCode 11.4. This can be used in the context of environment objects also.
我想将用户的设置表示为可观察对象应该是很常见的。因此,这里有一个保持可观察数据与用户默认值同步并针对 xCode 11.4 更新的示例。这也可以在环境对象的上下文中使用。
##代码##