NSKeyedArchiver et CLLocationCoordinate2D
Bonjour à tous
Je tente de sauvegarder des CLLocationCoordinate2D avec userdefaults (XCOde 9 et Swift 4)
J'ai une classe parkingSpot qui contient des infos dont le CLLocationCoordinate2D, un required init(coder aDecoder: NSCoder) et func encode(with aCoder: NSCoder)
Je sauvegarde mes données avec :
let postsData = NSKeyedArchiver.archivedData(withRootObject: parkedCarAnnotation!)
UserDefaults.standard.set(postsData, forKey: "posts")
UserDefaults.standard.synchronize()
Pas d'erreur à la compilation mais quand je veux tester l'app, paf le chien :
'NSInvalidArgumentException', reason: '*** -[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs'
J'ai trouvé des choses sur le net mais rien n'a pu m'aider à résoudre mon problème.
Auriez-vous une idée ?
Merci
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Bonjour,
this archiver cannot encode structs
Le message semble assez explicite. Il faudrait voir ta méthode encode mais tu dois essayer "serializer" une valeur qui ne peut pas l'être. Il faut dans ce cas transformer cette valeur en autre chose qui sera accepté par NSKeyedArchiver. Si c'est la structure CLLocationCoordinate2D il doit être possible de faire une transformation en String ou bien d'encoder lat et long distinctement. Lors de l'init il suffit de transformer à nouveau la chaine ou 'lat' et 'long' en CLLocationCoordinate2D.
Tu veux sauvegarder dans (NS)UserDefault une class custom et utiliser NSKeyedArchiver (et son pendant de désarchivage) ce qui transforme ta classe en (NS)Data.
Tu as bien implémenté le initWithCoder: et decodeWithCoder: (en bref respecter le NSCoding protocol), et tous les "sous-objets" doivent l'implémenter aussi.
La plupart des objets/types basiques le supportent, mais CLLocationCoordinate2D est une struct et ne le supporte pas, et il faut le faire à la main.
Comme il s'agit d'une simple structure avec deux double (qui est caché derrière un CLLocationDegrees), plusieurs choix s'offrent à toi, donc le plus simple, enregistrer la myLocation.latitude et myLocation.longitude lors de l'archive, et lors de la désarchive (dans initWithCoder:), les récupérer et d'appeler CLLocationCoordinate2DMake() avec.
Ou tu peux créer une extension pour Data qui marchera avec n'importe quelle struct :
Et, pour l'utiliser :
Bonne suggestion, il faut cependant faire très attention à l'évolution du contenu des structures.
Merci beaucoup pour vos réponses, je suis en train de les digérer...
::)
Bon ben la digestion est mauvaise et en plus je n'avais pas tout dit...
La petite app doit sauvegarder un emplacement avec une description et c'est un test pour incruster dans mon app principale plus quand j'aurais pigé comment faire.
Je tente de sauvegarder une custom class qui contient entre autre un CLLocationCoordinate2D et ça semble fonctionner.
​En revanche pas moyen de récupérer les données après kill de l'app...
Voici le code de Joanna que j'ai adapté sans tout comprendre c'est certain :
La custom classe :
Le code du button qui sauvegarde :
Et le code qui est censé recharger les données après un kill par exemple :
Pourquoi pas utiliser CoreData pour sauvegarder les données ?
Si tu penses que c'est mieux, je regarder mais ça semble encore plus compliqué pour un débutant que UserDefaults...
Donc ce que je fais ne peux pas fonctionner ?
Tu sauvegardes un ARRAY d'objets de type Data serializant chacune leur propre classe.
Tu lis ça comme un objet de type Data qui serializait un ParkingSpot.
C'est plus que confus.
Tu as un init(coder aDecoder: NSCoder!) et un initWithCoder(aDecoder: NSCoder) -> ParkingSpot (différence réelle ? à part peut-être du Swift 3 et du Swift inférieur ?)
Tout ceci :
devrait être :
Si parkedCarAnnotation est un objet de type ParkingSpot :
Sinon
PS : Il y a peut-être des ? et autres ! propres à Swift qui manque, mais je m'attache plus à la logique/concept.
Voici mon code :
Alors oui avec ton code ça fonctionne parfaitement ! C'est super !
Merci !
Je ne pensais pas ramer autant pour sauvegarder un "simple" emplacement...
Toute l'intelligence du code réside dans l'extension Data mais que hélas j'ai du mal à comprendre.
La fonction .to est appelée depuis la classe ParkingSport alors que l'extension est sur la classe Viewcontroller, par exemple.
Pourrais-tu me dire en deux mots ce que fait cette extension ?
La notation avec des T et des <T> ne me dit rien.
Merci encore
@Joanna Carter
Même si l'utilisation de Generics est bon, je préconiserais de ne pas les utiliser étant donné apparemment le niveau de @macphi. C'est un usage avancé dont il peut se passer pour l'instant tant qu'il maà®trise le reste basique du initWithCoder/decodeWithADecoder, NSKeyedArchive, NSKeyedUnarchive qui est le sujet actuellement.
Je sais que cela a d'autres utilisations, mais il a tout le temps d'apprendre d'autres concepts avant, car j'ai peur que cela reste un peu de la magie/boà®te noire, et les exemples donnés trop vagues/abstraits ou trop précis, risquant de créer des erreurs/confusions par la suite.
@macphi
Bon. Comme dit Larme, peut-être il y a les choses dans mon code qu'il faut laisser pout le moment ; l'extension sur Data utilise un technique qui s'appelle "generics" - ce que tu puisse oublier à ton niveau :-*
Du coup, j'ai refait le code pour la classe ParkingSpot avec un technique plus simple :
Merci beaucoup pour vos réponses !
Du coup effectivement les generics ça me dit quelque chose dans le bouquin Swift d'Apple vers la fin il me semble...
Il faudra donc que je révise !
Attention avec la classe Parkinson, c'est dangereux ce truc ..
Je constate que tu utilises Xcode 9 et Swift 4.
Dans ce cas là , il y a les nouveaux APIs qui sont plus simples et n'utilises plus les Strings pour les noms des propriétés que l'on encode/decode.
CLLocationCoordinate2D n'est pas encore automatiquement encodé et décodé mais, ce code ci-dessous, montre comment encoder/decoder les autres types qui ne sont pas encore pris en charge.
De mon avis, il est préférable de séparer l'idée d'un ParkingSpot de l'idée d'un annotation. Du coup, avec l'extension pour CLLocationCoordinate2D, nous pouvons créer une struct qui sera automatiquement encodé/décodé :
Puis, nous pouvons définir une class pour l'annotation :
Et, enfin, du code pour le tester :
Si tu préfères de mélanger le ParkingSpot avec l'annotation, c'est possible comme ci :
Et tu peux utiliser le même code de test qu'auparavant mais en oubliant le code qui crée l'annotation du ParkingSpot
C'est parfait, j'ai des devoirs à faire en rentrant ce soir !
J'ai rarement vu sur des forums des réponses aussi exhaustives.
Merci beaucoup !