Comment lire un fichier encodé avec une App mac dans une App iOS

iLandesiLandes Membre
mars 2016 modifié dans Xcode et Developer Tools #1

J'ai un problème avec ce code qui provoque l'émission du message : "The class may be defined in source code  or a library that is not linked"



if let readingDate = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [MoonDirection] {
moonDirections = readingDate
}


J'utilise une classe MoonDirection qui vient d'un autre project mais que j'ai mis (sans la copier) dans le projet dont est issu ce code.


 


La classe est bien reconnu mais il m'est impossible de faire fonctionner le code cité ci-dessus. Bien que le message de la console soit verbeux je ne le comprends pas. Pas mal de recherche sur le net n'ont rien éclairci.


 


D'avance, merci de votre aide.


Mots clés:

Réponses


  •  


    Quand le sage montre (la direction de) la lune avec le doigt, l'idiot regarde le doigt ..


     


    Si Xcode étais intelligent, cela se saurait ..

  • Il n'y a pas d'autres infos avec le message d'erreur ?

  • Merci pour votre intérêt porté à  ma requête. Le message complet est :


     


    GardeningByTheMoonViewer[2108:136966] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (GardeningByTheMoonEditor.MoonDirection) for key (NS.objects); the class may be defined in source code or a library that is not linked'


    *** First throw call stack:


     


     


    Ca à  donc quelque chose à  voir avec ma classe (MoonDirection) et NSKeyedUnarchiver. A priori plus avec NSKeyedUnarchiver car je peux utiliser ma classe correctement lorsque je l'initialise normalement...


  • Tu peux montrer le code de ta classe lunatique ?

  • Là  voilà , rien d'extraordinaire apparement :



    //
    // MoonDirection.swift
    // MoonDirectionEditor / GardeningByTheMoonViewer /
    //
    // Created by Sebastien REMY on 23/03/2016.
    // Copyright © 2016 Sebastien REMY. All rights reserved.
    //

    import Foundation


    private var dateFormatter: NSDateFormatter {
    get {
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss z"
    return dateFormatter
    }
    }


    class MoonDirection: NSObject, NSCoding {

    enum direction: Int32 {
    case undefinied = 0
    case ascending = 1
    case descending = 2
    }



    var moonDirection: Int32 = 0 {
    didSet {
    print ("Moon direction didset")
    if let _ = direction.init(rawValue: moonDirection) {}
    else {
    moonDirection = direction.undefinied.rawValue
    }
    }
    }

    var directionFromDate: NSDate? = NSDate() {
    didSet {
    print ("direction from set")
    }
    }

    // MARK: - NSCoding
    func encodeWithCoder(aCoder: NSCoder) {
    print ("encode")

    // From Date
    if let d = directionFromDate {
    // Convert date in String if specified)
    let stringDate = dateFormatter.stringFromDate(d)
    aCoder.encodeObject(stringDate, forKey: "directionFromDate")
    } else {
    aCoder.encodeObject(nil, forKey: "directionFromDate")
    }

    // Moon Direction
    aCoder.encodeInt(moonDirection, forKey: "moonDirection")
    }

    required init(coder aDecoder: NSCoder) {
    print ("Decode")
    // From Date
    if let d = aDecoder.decodeObjectForKey("directionFromDate") as! String? {
    let formatedDate = dateFormatter.dateFromString(d)
    directionFromDate = formatedDate
    } else {
    directionFromDate = nil
    }

    // Moon Direction
    moonDirection = aDecoder.decodeIntForKey("moonDirection")
    }

    override init() {
    super.init()
    }

    // MARK: - Printable
    override var description : String {
    return "MoonDirection : \(moonDirection) ; Date :\(directionFromDate)"
    }
    }

  • DrakenDraken Membre
    mars 2016 modifié #7

    Je suis loin d'être un spécialiste en encodage/encodage, mais cela me perturbe de voir que tu encodes un nil sous une clé. Il ne serait pas plus intelligent dans ce cas de ne rien encoder ? Et de tester l'absence ou la présence de la clé au décodage ? 



    if let d = directionFromDate {
    // Convert date in String if specified)
    let stringDate = dateFormatter.stringFromDate(d)
    aCoder.encodeObject(stringDate, forKey: "directionFromDate")
    } else {
    aCoder.encodeObject(nil, forKey: "directionFromDate")
    }

  • LexxisLexxis Membre
    mars 2016 modifié #8

    Je suppose que Swift considère que la classe MoonDirection du module GardeningByTheMoonEditor est différente de la classe du module GardeningByTheMoonViewer.

     



    NSKeyedUnarchiver.setClass("MoonDirection", forClassName:"GardeningByTheMoonEditor.MoonDirection")

    Cette ligne indique NSKeyedUnarchiver d'utiliser la classe de MoonDirection de l'application plutôt que celle donné par le module.

  • iLandesiLandes Membre
    mars 2016 modifié #9

    Plus je cherche moins je trouve. J'ai essayé de créer une classe identique directement dans mon projet viewer. J'ai le même message du coup je vais me replonger dans mes livres. Il me semble que dans ce que j'ai étudié ils implémentaient aussi NSCopying.


     


    Je continue mes recherches...


  • Apparemment tu essaie de charger un objet créer avec la même classe mais depuis une application différente et donc depuis une module différent.

    Peut être devrait-tu créer un framework contenant ta classe MoonDirection avec lequel tu pourrais effectuer la (dé)sérialization. Du coup le nom du module serait le même et tu n'aurais plus ce problème.


  • L'idée du framework me plaisait bien. Je n'en avais créé. Maintenant c'est fait mais cela ne résout pas mon problème. ;D



  •  


     


    Quand le sage montre (la direction de) la lune avec le doigt, l'idiot regarde le doigt ..

     


    Ce soir me vient une idée. Je change le titre du post et je me repose la question. Comme quoi Draken, les proverbes ont du bon quand on fait l'effort de les méditer un peu. Avec 4h30 de temps sans prise de courant j'ai eu le temps.


     


    Je repose donc mon problème, je ferais aussi des recherches sur google demain.


     


    Ce que je veux c'est lire des données encodé avec NSCode dans une application Mac (éditeur) et les lire dans une application iOS (viewer). 


     


    En attendant si vous avez des pistes je suis preneur...


  • MalaMala Membre, Modérateur
    mars 2016 modifié #13

    Recopies le fichier dans ton projet juste pour voir. C'est peut être tout bonnement un problème de path (Xcode est vraiment très très con des fois).


  • LexxisLexxis Membre
    mars 2016 modifié #14

    Pour moi c'est clairement un problème de scope (surtout vu le message et le fait que tu utilises Swift où les namespaces sont implicites).


     


    Si tu as créer un framework contenant la classe MoonDirection il faut exclusivement utiliser la classe du framework dans les deux applications. Tu peux même ajouter le nom du module lors de la création d'instance pour être certain d'utiliser la bonne classe et tu peux vérifier le nom de la classe utilisé en lisant le fichier sauvegardé avec un simple éditeur hexadécimal. (testé sous Xcode 7.3)


     


    As tu testé la méthode setClass de NSKeyedUnarchiver ? c'est surement le moyen le plus rapide pour relire le fichier (mais aussi le plus contraignant).


  • Joanna CarterJoanna Carter Membre, Modérateur
    mars 2016 modifié #15
    Petit tuyau. La classe est déclarée comme internal et, du coup, pas visible aux frameworks Apple, e.g. NSKeyedUnarchiver.


  • Petit tuyau. La classe est déclarée comme internal et, du coup, pas visible aux frameworks Apple, e.g. NSKeyedUnarchiver.




    Tu peux préciser Joana stp.

  • PyrohPyroh Membre
    mars 2016 modifié #17


    Petit tuyau. La classe est déclarée comme internal et, du coup, pas visible aux frameworks Apple, e.g. NSKeyedUnarchiver.




    Non.


     


    internal est l'access control par défault.


    ça s'applique au module et comme les frameworks sont linked au sein du module ils ont accès aux classes déclarées internal. Sinon on devrait tout déclarer public pour utiliser Cocoa(Touch)...


     


    Maintenant effectivement les classes déclarées internal ne seront pas accessibles dans un module externe. Et c'est là  le problème. Si tu as mis cette classe dans un framework (donc un module) que tu link dans un autre module il faut que ta classe soit soit déclarée comme public. (et je pense que c'est ce que tu voulais dire Joanna ;) )


     


    Il faudrait nous en dire un peu plus au niveau de l'archi de ton code.


  • Joanna CarterJoanna Carter Membre, Modérateur

    Maintenant effectivement les classes déclarées internal ne seront pas accessibles dans un module externe. Et c'est là  le problème. Si tu as mis cette classe dans un framework (donc un module) que tu link dans un autre module il faut que ta classe soit soit déclarée comme public. (et je pense que c'est ce que tu voulais dire Joanna ;) )




    Exacte ! :)
  • iLandesiLandes Membre
    avril 2016 modifié #19

    Le problème c'est qu'il me faut créer deux framework : un pour iOs et l'autre pour MacOs, du coup le problème ce déplace mais reste le même. Je vais devenir chèvre ou retrourner à  ma bonne vielle base SQL.


     


    PS : J'ai suivi ce lien :


     


    http://www.swift-studies.com/blog/2014/6/30/creating-a-pure-swift-framework-for-both-ios-and-mac


  • Comment lire un fichier encodé avec une App Mac, dans une App iOS ?


     


    Je ne comprend pas trop ton problème. Cela se fait tout seul, non ?


     


    Exemple :


    J'ai écrit une micro-application OSX stockant un texte dans un NSData, et le sauvant sur disque, dans le dossier Document du Mac.



    class MasterViewController: NSViewController {

    override func viewDidLoad() {
    super.viewDidLoad()
    // Do view setup here.

    let texte = "Sushis, Miam Miam !"
    let data = texte.dataUsingEncoding(NSUTF8StringEncoding)

    // Creation URL
    let fileName = "sushis.bin"
    let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
    let fileURL = documentsURL.URLByAppendingPathComponent(fileName)

    data?.writeToURL(fileURL, atomically: false)

    }

    }


    Ensuite j'ai écrit une nano-application iOS pour lire ce fichier.



    class ViewController: UIViewController {

    override func viewDidLoad() {
    super.viewDidLoad()

    // Creation URL
    let fileName = "sushis"
    let urlFileiOS = NSBundle.mainBundle().URLForResource(fileName, withExtension: "bin")
    if let url = urlFileiOS {
    // Lecture fichier à  partir du bundle de l'application
    let data = NSData(contentsOfURL: url)
    if let data = data {
    let text = String(data: data, encoding: NSUTF8StringEncoding)
    print (text)
    }
    }
    }
    }


    J'ouvre le répertoire Documente du Mac, je transfère le fichier sushis.bin dans l'application iOS, je compile, j'exécute et hop, le simulateur affiche :


     



     


    Optional("Sushis, Miam Miam !")



     


     


    Note technique : oui, je sais, il y a une pyramide de la mort dans mon code source.

  • Mon problème vient de NSCoder que j'utilise pour encoder mes objets. C'est son utilisation qui me propose problème. Je n'arrive pas à  décoder' sous iOS.
  • DrakenDraken Membre
    avril 2016 modifié #22

    Arg .. je retombe sur le même message d'erreur que toi ! Oui, cela doit provenir de NSCoder.  


    T'as essayé en mettant tes données dans un fichier XML ou Json ?


    Sinon, il reste la possibilité d'encoder les données à  la main, comme je le fait ici :


     


    http://forum.cocoacafe.fr/topic/14467-swiftlecture-aléatoire-nsdata-dans-un-fichier/

  • Joanna CarterJoanna Carter Membre, Modérateur
    avril 2016 modifié #23

    Seb, tu as plusieurs erreurs dans ton code. Voici une version qui est, au même temps plus facile et plus correct...



    //
    // MoonDirection.swift
    // FrameworkKit
    //
    // Created by Joanna Carter on 03/04/2016.
    // Copyright © 2016 Joanna Carter. All rights reserved.
    //


    import Foundation


    public class MoonDirection: NSObject
    {
    public enum Direction: Int32
    {
    case Undefined
    case Ascending
    case Descending
    }

    public var direction: Direction = .Undefined

    public var directionFromDate: NSDate? = NSDate()

    // MARK: - NSCoding

    public func encodeWithCoder(aCoder: NSCoder)
    {
    aCoder.encodeInt(direction.rawValue, forKey: "direction")

    aCoder.encodeObject(directionFromDate, forKey: "directionFromDate")
    }

    init(direction: Direction, directionFromDate: NSDate)
    {
    self.direction = direction

    self.directionFromDate = directionFromDate
    }

    public convenience override init()
    {
    self.init(direction: .Undefined, directionFromDate: NSDate())
    }

    public convenience init?(coder aDecoder: NSCoder)
    {
    guard let direction = aDecoder.decodeIntForKey("direction") as Int32?,
    let directionFromDate = aDecoder.decodeObjectForKey("directionFromDate") as? NSDate else
    {
    return nil
    }

    self.init(direction: Direction(rawValue: direction) ?? .Undefined, directionFromDate: directionFromDate)
    }
    }

    J'attends que tu l'aies essayé et me donnes les retours :-*


  • DrakenDraken Membre
    avril 2016 modifié #24

    J'ai trouvé !  <3 </p>

     


    Le NSData contient le nom complet de la classe l'ayant généré (NomApp.NomClasse). Pour l'utiliser dans une autre classe, il faut associer la nouvelle classe avec l'ancien nom.


     


    Par exemple, pour désarchiver dans la classe Planete, le contenu d'un NSData créé par la classe Planete de l'application EncodeOSX, il faut utiliser cette syntaxe :



    NSKeyedUnarchiver.setClass(Planete.self, forClassName: "EncodeOSX.Planete")

    Mise en pratique :



    class ViewController: UIViewController {

    override func viewDidLoad() {
    super.viewDidLoad()

    // Creation URL
    let fileName = "planete"
    let urlFileiOS = NSBundle.mainBundle().URLForResource(fileName, withExtension: "bin")
    if let url = urlFileiOS {
    // Lecture fichier
    let data = NSData(contentsOfURL: url)
    if let data = data {
    // ==> On indique le nom complet de la classe ayant générée le NSData
    NSKeyedUnarchiver.setClass(Planete.self, forClassName: "EncodeOSX.Planete")
    // On peut lire le NSData
    let planete = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Planete
    if let planete = planete {
    print ("Nom : ", planete.nom)
    print ("Masse : ", planete.masse)
    }
    }
    }
    }
    }


    ça fonctionne. Je viens de le tester, avec cette classe :



    class Planete: NSObject, NSCoding {
    var nom = " "
    var masse:Float = 1.0

    init(nom:String, masse:Float) {
    self.nom = nom
    self.masse = masse
    }

    func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(self.nom)
    aCoder.encodeObject(self.masse)
    }

    required init?(coder aDecoder: NSCoder) {
    self.nom = aDecoder.decodeObject() as! String
    self.masse = aDecoder.decodeObject() as! Float
    }
    }


    L'info produite sous OSX est bien récupérée avec l'application iOS :


     



     


    Nom :  Mars la Rouge


    Masse :  0.107


     


     


    Ouf !


     


    En fait la question n'était pas de passer d'une application OSX à  une application iOS, mais d'une application à  une autre. Il se produit le même problème de nom de classe entre deux applications OSX. 

  • Pourquoi ne pas faire un framework commun à  iOS et à  OSX ? (Un framework qui ne s'occuperai que de ça).


    Parce que là  si tu commence à  hardcoder ce genre de choses ça va être super marrant à  maintenir...


  • Nouvelle version de l'exemple plus lisible, sans la pyramide de l'enfer :



    class ViewController: UIViewController {

    override func viewDidLoad() {
    super.viewDidLoad()

    // On indique le nom complet de classe ayant générée le NSData
    NSKeyedUnarchiver.setClass(Planete.self, forClassName: "EncodeOSX.Planete")

    let fileName = "planete"
    if let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: "bin"),
    let data = NSData(contentsOfURL: url),
    let planete = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Planete
    {
    print ("Nom : ", planete.nom)
    print ("Masse : ", planete.masse)
    }
    }
    }

  • Joanna CarterJoanna Carter Membre, Modérateur
    avril 2016 modifié #27

    Je viens de faire un test avec un workspace, où se trouvent un framework avec deux targets, un app iOS et un app OS X.


     


    Le framework ne contient que le code que je t'ai donné plutôt.


     


    Les deux apps peuvent créer et lire le même fichier, étant donné qu'il faut le copier du dossier Documents de l'un vers l'autre.


     


    Le code pour les deux apps et copié/collé et c'est comme-ci...



    {
    let moonDirection = MoonDirection()

    moonDirection.direction = .Undefined

    let savedData = NSKeyedArchiver.archivedDataWithRootObject(moonDirection!)

    let docPath = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]

    let docURL = docPath.URLByAppendingPathComponent("Direction.dat")

    do
    {
    try savedData.writeToURL(docURL, options: .AtomicWrite)
    }
    catch
    {
    print("error writing")
    }

    if let readData = NSData(contentsOfURL: docURL)
    {
    let moonDirection = NSKeyedUnarchiver.unarchiveObjectWithData(readData) as? MoonDirection

    print(moonDirection)
    }
    }

    Tu peux mettre en commentaire, soit l'écriture, soit la lecture, selon tes souhaites


  • Merci à  tous,


     


    Je code pour mon plaisir, je ne pourrais m'y remettre que plus tard dans la semaine. 


Connectez-vous ou Inscrivez-vous pour répondre.