82 votes

Enregistrer la structure dans UserDefaults

J'ai une structure que je veux enregistrer dans UserDefaults. Voici ma structure

 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"),
]
 

Dans un autre ViewController, j'ai un UIButton qui s'ajoute à cette structure comme

 @IBAction func likeButtonPressed(_ sender: Any) {   
   songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))
}
 

Je le veux afin que chaque fois que l'utilisateur clique sur ce bouton également, il enregistre la structure dans UserDefaults afin que chaque fois que l'utilisateur quitte l'application, puis l'ouvre à nouveau, il soit enregistré. Comment je ferais ça?

284voto

matt Points 60113

Dans Swift 4, c'est à peu près trivial. Rendez votre structure codable simplement en la marquant comme adoptant le protocole Codable:

 struct Song:Codable {
    var title: String
    var artist: String
}
 

Commençons maintenant avec quelques données:

 var songs: [Song] = [
    Song(title: "Title 1", artist: "Artist 1"),
    Song(title: "Title 2", artist: "Artist 2"),
    Song(title: "Title 3", artist: "Artist 3"),
]
 

Voici comment obtenir cela dans UserDefaults:

 UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
 

Et voici comment le récupérer plus tard:

 if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
    let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}
 

22voto

YannickSteph Points 7353

Ceci est mon extension UserDefaults dans le thread principal , pour définir l'objet get Codable dans 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)
    }
}
 

Mise à jour Ceci est mon extension UserDefaults en arrière - plan, pour définir l' objet se codable dans 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)
    }
}
 

16voto

vadian Points 29149

Si la structure contient uniquement des biens de la liste conforme propriétés je recommande d'ajouter une propriété propertyListRepresentation et init méthode

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]
    }
}

Pour enregistrer un tableau de chansons UserDefaults écrire

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

Pour lire le tableau

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

Si title et artist ne sera jamais muté envisager de déclarer les propriétés comme des constantes (let) .


Cette réponse a été rédigé alors que Swift 4 était en état de beta. Pendant ce temps conforme à l' Codable est la meilleure solution.

3voto

Dark Innocence Points 1002

Si vous essayez simplement de sauvegarder ce tableau de chansons dans UserDefaults et que rien d’extraordinaire n’est utile: -

 //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
 

Si vous stockez un tableau lourd, je vous suggère d’utiliser le protocole NSCoding ou le protocole codable dans swift 4

Exemple de protocole de codage: -

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

}
 

3voto

Valdmer Points 191

À partir d' ici:

Un objet par défaut doit être un bien qui est une instance de (ou pour les collections, une combinaison d'instances d'): NSData , NSString , NSNumber , NSDate , NSArray ou NSDictionary . Si vous souhaitez stocker n'importe quel autre type d'objet, vous devez généralement archive pour créer une instance de NSData.

Vous devez utiliser NSKeydArchiver. La Documentation peut être trouvée ici et des exemples ici et ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X