我有一个我要保存到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,这样每当用户退出应用程序然后打开它时,它就会被保存。我该怎么做?
答案 0 :(得分:199)
在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)
}
答案 1 :(得分:14)
这是我在主线程中的 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)
}
}
更新这是后台的UserDefaults扩展程序,将get 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)
}
}
答案 2 :(得分:13)
如果结构只包含属性列表兼容属性,我建议添加属性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{ $0.propertyListRepresentation }
UserDefaults.standard.set(propertylistSongs, forKey: "songs")
读取数组
if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] {
songs = propertylistSongs.flatMap{ Song(dictionary: $0) }
}
如果title
和artist
永远不会发生变异,请考虑将属性声明为常量(let
)。
这个答案是在Swift 4处于测试状态时编写的。同时符合Codable
是更好的解决方案。
答案 3 :(得分:2)
如果您只是想在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")
}
}
答案 4 :(得分:2)
这是现代的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]
答案 5 :(得分:1)
来自here:
默认对象必须是属性列表 - 即(或集合的实例组合)的实例: 的NSData , 的NSString , 的NSNumber , 的NSDate , NSArray的 , 要么 的NSDictionary 。如果要存储任何其他类型的对象,通常应将其存档以创建NSData实例。
答案 6 :(得分:1)
我认为将用户的设置表示为可观察对象应该很普遍。因此,这是一个使可观察数据与用户默认值保持同步并针对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"
}
}
答案 7 :(得分:0)
雨燕5
如果您只需要使用数据格式将结构保存为 UserDefault 。
Sample结构
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)
}
}
有两种类型可以转换为数据
首先,我们使用 Codable(可编码和可解码)保存结构
保存值示例
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)
为方便起见,您可以使用任何类型将结构保存到userDefault中。
答案 8 :(得分:0)
这是一个更简单的解决方案
@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
}