web-dev-qa-db-fra.com

Comment enregistrer un tableau d'objets dans NSUserDefault avec Swift?

c'est une classe Place j'ai défini:

class Place: NSObject {

    var latitude: Double
    var longitude: Double

    init(lat: Double, lng: Double, name: String){
        self.latitude = lat
        self.longitude = lng
    }

    required init(coder aDecoder: NSCoder) {
        self.latitude = aDecoder.decodeDoubleForKey("latitude")
        self.longitude = aDecoder.decodeDoubleForKey("longitude")
    }

    func encodeWithCoder(aCoder: NSCoder!) {
        aCoder.encodeObject(latitude, forKey: "latitude")
        aCoder.encodeObject(longitude, forKey: "longitude")
    }

}

Voici comment j'ai essayé d'enregistrer un tableau de Places:

var placesArray = [Place]

//...

func savePlaces() {
    NSUserDefaults.standardUserDefaults().setObject(placesArray, forKey: "places")
    println("place saved")
}

Cela n'a pas fonctionné, voici ce que j'obtiens sur la console:

Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')

Je suis nouveau sur iOS, pourriez-vous m'aider?

DEUXIÈME ÉDITION

J'ai trouvé une solution pour sauvegarder les données:

func savePlaces(){
    let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
   NSUserDefaults.standardUserDefaults().setObject(myData, forKey: "places")
    println("place saved")
}

Mais j'obtiens une erreur lors du chargement des données avec ce code:

 let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData

 if placesData != nil {
      placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData!) as [Place]
 }

l'erreur est:

[NSKeyedUnarchiver decodeDoubleForKey:]: value for key (latitude) is not a double number'

Je suis sûr d'avoir archivé un Double, il y a un problème avec le processus de sauvegarde/chargement

Un indice?

27
Cherif

À partir du Guide de programmation de la liste des propriétés :

Si un objet de liste de propriétés est un conteneur (c'est-à-dire un tableau ou un dictionnaire), tous les objets qu'il contient doivent également être des objets de liste de propriétés. Si un tableau ou un dictionnaire contient des objets qui ne sont pas des objets de liste de propriétés, vous ne pouvez pas enregistrer et restaurer la hiérarchie de données à l'aide des différentes méthodes et fonctions de liste de propriétés.

Vous devrez convertir l'objet vers et depuis une instance NSData à l'aide de NSKeyedArchiver et NSKeyedUnarchiver.

Par exemple:

func savePlaces(){
    let placesArray = [Place(lat: 123, lng: 123, name: "hi")]
    let placesData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
    NSUserDefaults.standardUserDefaults().setObject(placesData, forKey: "places")
}

func loadPlaces(){
    let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData

    if let placesData = placesData {
        let placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData) as? [Place]

        if let placesArray = placesArray {
            // do something…
        }

    }
}
28
Aaron Brager

Swift 4

Nous devons sérialiser notre objet Swift pour l'enregistrer dans userDefaults.

Dans Swift 4, nous pouvons utiliser le protocole Codable, ce qui nous facilite la vie sur la sérialisation et l'analyse JSON

Workflow (Enregistrer Swift dans UserDefaults):

  1. Confirmez le protocole codable pour modéliser la classe (classe Lieu: codable).
  2. Créez un objet de classe.
  3. Sérialisez cette classe à l'aide de la classe JsonEncoder.
  4. Enregistrez l'objet sérialisé (données) dans UserDefaults.

Workflow (Get Swift object from UserDefaults):

  1. Obtenir des données à partir de UserDefaults (qui renverra un objet sérialisé (données))
  2. Décoder des données à l'aide de la classe JsonDecoder

Code Swift 4:

class Place: Codable {
    var latitude: Double
    var longitude: Double

    init(lat : Double, long: Double) {
        self.latitude = lat
        self.longitude = long
    }

    public static func savePlaces(){
        var placeArray = [Place]()
        let place1 = Place(lat: 10.0, long: 12.0)
        let place2 = Place(lat: 5.0, long: 6.7)
        let place3 = Place(lat: 4.3, long: 6.7)
        placeArray.append(place1)
        placeArray.append(place2)
        placeArray.append(place3)
        let placesData = try! JSONEncoder().encode(placeArray)
        UserDefaults.standard.set(placesData, forKey: "places")
    }

    public static func getPlaces() -> [Place]?{
        let placeData = UserDefaults.standard.data(forKey: "places")
        let placeArray = try! JSONDecoder().decode([Place].self, from: placeData!)
        return placeArray
    }
}
23

Swift 3 & 4

Voici l'exemple de code complet dans Swift 3 & 4.

import Foundation

class Place: NSObject, NSCoding {

    var latitude: Double
    var longitude: Double
    var name: String

    init(latitude: Double, longitude: Double, name: String) {
        self.latitude = latitude
        self.longitude = longitude
        self.name = name
    }

    required init?(coder aDecoder: NSCoder) {
        self.latitude = aDecoder.decodeDouble(forKey: "latitude")
        self.longitude = aDecoder.decodeDouble(forKey: "longitude")
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(latitude, forKey: "latitude")
        aCoder.encode(longitude, forKey: "longitude")
        aCoder.encode(name, forKey: "name")
    }
}

func savePlaces() {
    var placesArray: [Place] = []
    placesArray.append(Place(latitude: 12, longitude: 21, name: "place 1"))
    placesArray.append(Place(latitude: 23, longitude: 32, name: "place 2"))
    placesArray.append(Place(latitude: 34, longitude: 43, name: "place 3"))

    let placesData = NSKeyedArchiver.archivedData(withRootObject: placesArray)
    UserDefaults.standard.set(placesData, forKey: "places")
}

func loadPlaces() {
    guard let placesData = UserDefaults.standard.object(forKey: "places") as? NSData else {
        print("'places' not found in UserDefaults")
        return
    }

    guard let placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as? [Place] else {
        print("Could not unarchive from placesData")
        return
    }

    for place in placesArray {
        print("")
        print("place.latitude: \(place.latitude)")
        print("place.longitude: \(place.longitude)")
        print("place.name: \(place.name)")
    }
}

Exemple d'utilisation:

savePlaces()
loadPlaces()

Sortie console:

place.latitude: 12.0
place.longitude: 21.0
place.name: 'place 1'

place.latitude: 23.0
place.longitude: 32.0
place.name: 'place 2'

place.latitude: 34.0
place.longitude: 43.0
place.name: 'place 3'
18
Mobile Dan

Dans Swift 4.0+, nous pouvons utiliser l'alias de type Codable qui se compose de 2 protocoles: Decodable & Encodable.

Pour plus de commodité, j'ai créé un décodage générique et des méthodes de codage dont le type est contraint à Codable:

extension UserDefaults {
    func decode<T : Codable>(for type : T.Type, using key : String) -> T? {
        let defaults = UserDefaults.standard
        guard let data = defaults.object(forKey: key) as? Data else {return nil}
        let decodedObject = try? PropertyListDecoder().decode(type, from: data)
        return decodedObject
    }

    func encode<T : Codable>(for type : T, using key : String) {
        let defaults = UserDefaults.standard
        let encodedData = try? PropertyListEncoder().encode(type)
        defaults.set(encodedData, forKey: key)
        defaults.synchronize()
    }
}

Utilisation - sauvegarde d'un objet/tableau/dictionnaire:

Disons que nous avons un objet personnalisé:

struct MyObject: Codable {
    let counter: Int
    let message: String
}

et nous en avons créé une instance:

let myObjectInstance = MyObject(counter: 10, message: "hi there")

En utilisant l'extension générique ci-dessus, nous pouvons maintenant enregistrer cet objet comme suit:

UserDefaults.standard.encode(for: myObjectInstance, using: String(describing: MyObject.self))

Enregistrement d'un tableau du même type:

UserDefaults.standard.encode(for:[myFirstObjectInstance, mySecondObjectInstance], using: String(describing: MyObject.self))

Enregistrement d'un dictionnaire avec ce type:

let dictionary = ["HashMe" : myObjectInstance]
UserDefaults.standard.encode(for: dictionary, using: String(describing: MyObject.self))

Utilisation - chargement d'un objet/tableau/dictionnaire:

Chargement d'un seul objet:

let myDecodedObject = UserDefaults.standard.decode(for: MyObject.self, using: String(describing: MyObject.self))

Chargement d'un tableau du même type:

let myDecodedObject = UserDefaults.standard.decode(for: [MyObject].self, using: String(describing: MyObject.self))

Chargement d'un dictionnaire avec ce type:

let myDecodedObject = UserDefaults.standard.decode(for: ["HashMe" : myObjectInstance].self, using: String(describing: MyObject.self))
7
OhadM

Vous avez préparé Place pour l'archivage, mais vous supposez maintenant qu'un tableau de Place sera archivé automatiquement lorsque vous le stockez dans NSUserDefaults. Ce ne sera pas le cas. Vous devez l'archiver. Le message d'erreur vous le dit. Les seules choses qui peuvent être enregistrées dans NSUserDefaults sont des objets avec des types de liste de propriétés: NSArray, NSDictionary, NSString, NSData, NSDate et NSNumber. Un objet Place n'en fait pas partie.

Au lieu d'essayer d'enregistrer le tableau directement, l'archivez . Maintenant, c'est un NSData - et c'est l'un des types d'objets de liste de propriétés:

let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)

Vous pouvez maintenant stocker myData dans NSUserDefaults, car il s'agit d'un NSData. Bien sûr, lorsque vous le retirez à nouveau, vous devrez également le désarchiver pour le transformer d'un NSData en un tableau de Place.

EDIT : Soit dit en passant, il me semble, après coup, que votre classe Place peut avoir à adopter explicitement le protocole NSCoding pour que cela fonctionne. Vous semblez avoir omis cette étape.

7
matt

Voici le code que j'utilise. J'espère que cela vous aidera.

Considérez Sport est l'objet. Au début, nous devons confirmer la classe Codable à la classe d'objet comme ci-dessous.

class Sport: Codable {
    var id: Int!
    var name: String!
}

Ensuite, nous pourrions simplement utiliser le code suivant pour enregistrer le tableau de l'objet à l'aide de UserDefaults.

func saveSports(_ list:[Sport]) {
        UserDefaults.standard..set(try? PropertyListEncoder().encode(list), forKey:"KEY")
        UserDefaults.standard..synchronize()
    }

Maintenant, nous pouvons simplement passer la liste des objets en tant que paramètre et enregistrer en utilisant la méthode saveSports ()

Pour récupérer les données enregistrées, nous pourrions utiliser le code suivant.

func getSports() -> [Sport]? {
    if let data = UserDefaults.standard.value(forKey:"KEY") as? Data {
        let decodedSports = try? PropertyListDecoder().decode([Sport].self, from: data)
        return decodedSports
    }
    return nil
}

La méthode getSports () renvoie les données enregistrées.

0
Rahul Kavungal