webentwicklung-frage-antwort-db.com.de

Speichern Sie Struct in UserDefaults

Ich habe eine Struktur, die ich in UserDefaults speichern möchte. Hier ist meine Struktur

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 einem anderen ViewController habe ich ein UIButton, das an diese Struktur anhängt

@IBAction func likeButtonPressed(_ sender: Any) {

   songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))

   }

Ich möchte es so, dass, wenn der Benutzer auch auf diese Schaltfläche klickt, die Struktur in UserDefaults gespeichert wird. Wenn der Benutzer die Anwendung beendet und erneut öffnet, wird sie gespeichert. Wie würde ich das machen?

36
Jacob Cavin

In Swift 4 ist das ziemlich trivial. Machen Sie Ihre Struktur codierbar, indem Sie sie als codierbares Protokoll kennzeichnen:

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

Beginnen wir nun mit einigen Daten:

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

So können Sie UserDefaults so anpassen:

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

Und so geht es später wieder raus:

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

Wenn die Struktur nur Eigenschaftenlisten-kompatible Eigenschaften enthält, empfehle ich, eine Eigenschaft propertyListRepresentation und eine entsprechende init-Methode hinzuzufügen

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

Speichern eines Arrays von Songs in UserDefaults write

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

Um das Array zu lesen

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

Wenn title und artist niemals mutiert werden, sollten Sie die Eigenschaften als Konstanten (let) deklarieren.


Diese Antwort wurde geschrieben, während sich Swift 4 im Betastatus befand. Inzwischen ist die Anpassung an Codable die bessere Lösung.

13
vadian

Dies ist meine UserDefaults-Erweiterung im Hauptthread , um das get Codable object in UserDefaults zu setzen

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

Update Dies ist meine UserDefaults-Erweiterung im Hintergrund , um get Codable object in UserDefaults zu setzen

// 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)
    }
}
11
YannickSteph

Wenn Sie gerade versuchen, dieses Array von Songs in UserDefaults zu speichern, und nichts Besonderes, verwenden Sie Folgendes: -

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

Wenn Sie ein starkes Array speichern, sollten Sie das NSCoding-Protokoll oder das Codable Protocol in Swift 4 verwenden

Beispiel eines Codierungsprotokolls: -

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

}
2
Dark Innocence

Ab hier:

Ein Standardobjekt muss eine Eigenschaftsliste sein, d. H. Eine Instanz von (oder für Sammlungen eine Kombination von Instanzen von): NSData , NSString , NSNumber , NSDate , NSArray , oder NSDictionary . Wenn Sie einen anderen Objekttyp speichern möchten, sollten Sie ihn normalerweise archivieren, um eine Instanz von NSData zu erstellen.

Sie müssen NSKeydArchiver verwenden. Dokumentation ist hier und Beispiele hier und hier .

1
Valdmer