web-dev-qa-db-fra.com

archiver un tableau de structures optionnelles avec NSCoding dans Swift?

J'ai effectué beaucoup d'archivage NSCoding dans Obj-C, mais je ne sais pas comment il gère les structures dans Swift, ni les tableaux avec des valeurs facultatives. Voici mon code:

public struct SquareCoords {
    var x: Int, y: Int
}

et voici la classe que j'ai besoin de stocker:

public class Player: NSCoding {
    var playerNum: Int
    var name = ""
    private var moveHistory: [SquareCoords?] = []

    init (playerNum: Int, name: String) {
        self.playerNum = playerNum
        self.name = name
    }

    public required init(coder aDecoder: NSCoder!) {
        playerNum = aDecoder.decodeIntegerForKey("playerNumKey")
        name = aDecoder.decodeObjectForKey("nameKey") as String
        moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as [SquareCoords?]
    }

    public func encodeWithCoder(aCoder: NSCoder!) {
        aCoder.encodeInteger(playerNum, forKey: "playerNumKey")
        aCoder.encodeObject(name, forKey: "nameKey")
        aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")
    }
...

Sur la dernière ligne du codeur init, j'obtiens le message d'erreur suivant dans XCode:

'AnyObject' is not convertible to [SquareCoords?]'

et sur la dernière ligne de encodeWithEncoder:

Extra argument 'forKey' in call

Quelqu'un peut-il me faire avancer dans la bonne direction?

19
Chuck Smith

Je ne sais pas quel est le problème exact, mais si vous utilisez NSMutableArray plutôt qu'un tableau Swift, le problème est résolu:

public struct SquareCoords {
    var x: Int, y: Int
}



public class Player: NSCoding {
    var playerNum: Int
    var name = ""
    var moveHistory: NSMutableArray = NSMutableArray()

    init (playerNum: Int, name: String) {
        self.playerNum = playerNum
        self.name = name
    }

    public required init(coder aDecoder: NSCoder!) {
        playerNum = aDecoder.decodeIntegerForKey("playerNumKey")
        name = aDecoder.decodeObjectForKey("nameKey") as String
        moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as NSMutableArray
    }

    public func encodeWithCoder(aCoder: NSCoder!) {
        aCoder.encodeInteger(playerNum, forKey: "playerNumKey")
        aCoder.encodeObject(name, forKey: "nameKey")
        aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")
    }
}

Il semble que lorsque aDecoder.decodeObjectForKey retourne un AnyObject implicitement non enveloppé, il ne sera pas converti en tableau SquareCoords.

Cela fait un peu plus loin que ça et j’ai remarqué que cela avait peut-être quelque chose à voir avec l’utilisation d’une structure. (Vous créez un tableau de structures qui sont des types de valeur.) C’est un peu approximatif, mais j’ai remarqué que si un type de classe est utilisé pour SquareCoords, il n’ya pas de problème, par exemple.

public class SquareCoords {
    var x: Int = 0, y: Int = 0
}



public class Player: NSCoding {
    var playerNum: Int
    var name = ""
    private var moveHistory: [SquareCoords] = [SquareCoords]()

init (playerNum: Int, name: String) {
    self.playerNum = playerNum
    self.name = name
}

public required init(coder aDecoder: NSCoder!) {
    playerNum = aDecoder.decodeIntegerForKey("playerNumKey")
    name = aDecoder.decodeObjectForKey("nameKey") as String
    moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as [SquareCoords]
}

public func encodeWithCoder(aCoder: NSCoder!) {
    aCoder.encodeInteger(playerNum, forKey: "playerNumKey")
    aCoder.encodeObject(name, forKey: "nameKey")
    aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")
    }
}

Peut-être que la conversion de AnyObject échoue dans un tableau struct pour une raison quelconque. - Je suis sûr que quelqu'un d'autre peut fournir plus de perspicacité, espérons que cela aide un peu! Swift peut être tempétueux: D

10
Woodstock

Dans Le langage de programmation Swift , Apple déclare:

Swift fournit deux alias de types spéciaux pour travailler avec des types non spécifiques:
- AnyObject peut représenter une instance de tout type de classe.
- Any peut représenter une instance de tout type, y compris des types de fonction.

Sachant cela, tapez SquareCoords (Swift Structure) et [SquareCoords] (Tableau Swift de Swift Structure) ne peuvent pas être conformes au protocole AnyObject.

Par ailleurs, decodeObjectForKey: requiert un paramètre conforme au protocole AnyObject et encodeObject:forKey: renvoie AnyObject. Ainsi, les deux lignes suivantes ne peuvent pas être compilées:

moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as [SquareCoords?]
aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")

Par conséquent, à moins que vous ne trouviez un moyen de rendre SquareCoords conforme au protocole AnyObject (je ne sais pas si cela est possible), vous devrez transformer SquareCoords de Swift Structure en classe.

PS: À ce stade, vous pouvez demander: "OK, mais comment se peut-il que le type String - qui est en fait un Swift Struct - puisse se conformer au protocole AnyObject?" Eh bien, c’est parce que String est relié de manière transparente à la classe NSString de Foundation (Array, Dictionary sont pontés vers NSArray et NSDictionary de la même manière). Lisez cet article de blog si vous voulez avoir un meilleur aperçu.

27
Imanou Petit

Les autres réponses données ici résolvent votre problème. Cependant, j’ai récemment rencontré un problème similaire en essayant d’archiver mes structures Swift et j’ai trouvé un moyen intéressant de résoudre ce problème. NSCoding ne supporte pas les structures.

La méthode suivante consiste essentiellement à convertir les propriétés de la structure en éléments de dictionnaire. Mais, il fait cela élégamment en utilisant des protocoles. Tout ce que vous avez à faire est de définir un protocole qui implémente deux méthodes qui facilitent la création d'un dictionnaire et la suppression d'un dictionnaire de votre structure. L’utilisation des protocoles et des génériques présente l’avantage de pouvoir fonctionner lorsqu'une structure est une propriété d’une autre structure. Cette imbrication pourrait être à n'importe quelle profondeur.

J'appelle le protocole 'Dictionariable', qui indique que tout ce qui est conforme au protocole peut être converti en dictionnaire. La définition va comme ci-dessous.

protocol Dictionariable {
    func dictionaryRepresentation() -> NSDictionary
    init?(dictionaryRepresentation: NSDictionary?)
}

Maintenant, considérons une structure 'Movie'

struct Movie {
    let name: String
    let director: String
    let releaseYear: Int
}

Permettez-moi d'étendre la structure et de la rendre conforme au protocole "Dictionariable".

extension Movie: Dictionariable {

    func dictionaryRepresentation() -> NSDictionary {
        let representation: [String: AnyObject] = [
            "name": name,
            "director": director,
            "releaseYear": releaseYear
        ]
        return representation
    }

    init?(dictionaryRepresentation: NSDictionary?) {
        guard let values = dictionaryRepresentation else {return nil}
        if let name = values["name"] as? String,
            director = values["director"] as? String,
            releaseYear = values["releaseYear"] as? Int {
                self.name = name
                self.director = director
                self.releaseYear = releaseYear
        } else {
            return nil
        }
    }
}

Fondamentalement, nous avons maintenant un moyen de convertir en toute sécurité une structure en dictionnaire. Je dis sûr car nous implémentons la manière dont le dictionnaire est formé, propre à chaque structure. Tant que cette implémentation est correcte, la fonctionnalité fonctionnerait. Le moyen de récupérer la structure du dictionnaire consiste à utiliser un initialiseur disponible. Il doit être disponible, car la corruption des fichiers et d'autres raisons pourraient rendre l'instanciation de la structure incomplète. Cela ne se produira peut-être jamais, mais il est plus sûr que ce soit accessible.

func extractStructuresFromArchive<T: Dictionariable>() -> [T] {
    guard let encodedArray = NSKeyedUnarchiver.unarchiveObjectWithFile(path()) as? [AnyObject] else {return []}
    return encodedArray.map{$0 as? NSDictionary}.flatMap{T(dictionaryRepresentation: $0)}
}

func archiveStructureInstances<T: Dictionariable>(structures: [T]) {
    let encodedValues = structures.map{$0.dictionaryRepresentation()}
    NSKeyedArchiver.archiveRootObject(encodedValues, toFile: path())
}

//Method to get path to encode stuctures to
func path() -> String {
    let documentsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).first
    let path = documentsPath?.stringByAppendingString("/Movie")
    return path!
}

Les deux méthodes ci-dessus peuvent archiver et désarchiver un tableau de structure 'Movie'. Tout ce que vous devez faire, c'est mettre en œuvre le protocole «Dictionnaires» dans chacune de vos structures devant être archivées.

Vérifiez sur cet article de blog J'ai écrit en comparant trois façons d'archiver et de désarchiver les structures Swift. Il existe une implémentation plus détaillée et un fichier de terrain de jeu du code expliqué ci-dessus que vous pouvez exécuter et tester dans le lien.

8
Vishal V. Shekkar

Outre que NSCoding ne prend pas en charge les structures décrites ci-dessus, je suggère que votre classe hérite également de NSObject afin de pouvoir s’encoder et se décoder ainsi que ses propriétés.

0
JohnnyHockey