Save Struct to UserDefaults Save Struct to UserDefaults ios ios

Save Struct to UserDefaults


In Swift 4 this is pretty much trivial. Make your struct codable simply by marking it as adopting the Codable protocol:

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.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)}


This is my UserDefaults extension in main thread, to set get Codable object into UserDefaults

// MARK: - UserDefaults extensionspublic 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 This is my UserDefaults extension in background, to set get Codable object into UserDefaults

// MARK: - JSONDecoder extensionspublic 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 extensionspublic 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)    }}


If the struct contains only property list compliant properties I recommend to add a property propertyListRepresentation and a corresponding init method

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

let propertylistSongs = songs.map{ $0.propertyListRepresentation }UserDefaults.standard.set(propertylistSongs, forKey: "songs")

To read the array

if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] {    songs = propertylistSongs.flatMap{ Song(dictionary: $0) }}

If title and artist will never be mutated consider to declare the properties as constants (let) .


This answer was written while Swift 4 was in beta status. Meanwhile conforming to Codable is the better solution.