将Struct保存到UserDefaults

时间:2017-07-03 00:43:07

标签: ios swift struct nsuserdefaults

我有一个我要保存到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,这样每当用户退出应用程序然后打开它时,它就会被保存。我该怎么做?

9 个答案:

答案 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) }
}

如果titleartist永远不会发生变异,请考虑将属性声明为常量(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实例。

您需要使用NSKeydArchiver。可以找到文档here以及示例herehere

答案 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)
      }
    }

有两种类型可以转换为数据

  1. 可编码(可编码和可解码)。
  2. PropertyListEncoder和PropertyListDecoder

首先,我们使用 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

}