arrays - archivar matriz de estructuras opcionales con NSCoding en Swift?
optional (4)
He hecho un montón de archivado NSCoding en Obj-C, pero no estoy seguro de cómo maneja las estructuras en Swift, ni las matrices con valores opcionales. Aquí está mi código:
public struct SquareCoords {
var x: Int, y: Int
}
y aquí está la clase que necesito guardar:
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")
}
...
En la última línea del codificador init, aparece el siguiente mensaje de error en XCode:
''AnyObject'' is not convertible to [SquareCoords?]''
y en la última línea de encodeWithEncoder:
Extra argument ''forKey'' in call
¿Alguien puede hacer que me mueva en la dirección correcta?
Además de que NSCoding no admite estructuras como se mencionó anteriormente, mi sugerencia es que su clase también debe heredar de NSObject para poder codificarse y decodificarse a sí misma y sus propiedades.
En el lenguaje de programación Swift , Apple afirma:
Swift proporciona dos alias de tipos especiales para trabajar con tipos no específicos:
-AnyObject
puede representar una instancia de cualquier tipo de clase.
-Any
puede representar una instancia de cualquier tipo, incluidos los tipos de funciones.
Sabiendo eso, escriba SquareCoords
(Swift Structure) y escriba [SquareCoords]
(Swift array of Swift Structure) no puede cumplir con el protocolo AnyObject
.
Por otro lado, decodeObjectForKey:
requiere un parámetro que se ajuste al protocolo AnyObject
, y encodeObject:forKey:
devuelve AnyObject
. Por lo tanto, las dos líneas siguientes no se pueden compilar:
moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as [SquareCoords?]
aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")
Por lo tanto, a menos que encuentre una manera de hacer que SquareCoords
ajuste al protocolo AnyObject
(no sé si es posible), tendrá que transformar SquareCoords
de Swift Structure a Class.
PD: En este punto, puede preguntar: "Está bien, pero ¿cómo es posible que el tipo String
, que en realidad es un Swift Struct, pueda ajustarse al protocolo AnyObject
?" Bueno, eso se debe a que String
se une perfectamente a la clase NSString
de NSString
( Array
, Dictionary
se conectan a NSArray
y NSDictionary
la misma manera). Lee esta publicación del blog si quieres verlo mejor.
Las otras respuestas dadas aquí resuelven tu problema. Pero, enfrenté un problema similar recientemente al intentar archivar mis estructuras Swift y se me ocurrió una forma interesante de resolver esto. NSCoding no soporta estructuras.
Esencialmente, el siguiente método consiste en convertir las propiedades de la estructura en elementos del diccionario. Pero, lo hace elegantemente utilizando protocolos. Todo lo que necesita hacer es definir un protocolo que implemente dos métodos que ayuden a clasificar y anular el diccionario de su estructura. La ventaja de usar protocolos y genéricos es que funciona cuando una estructura es propiedad de otra estructura. Esta anidación podría ser a cualquier profundidad.
Llamo al protocolo ''Dictionariable'', que indica que cualquier cosa que se ajuste al protocolo se puede convertir en un diccionario. La definición es la siguiente.
protocol Dictionariable {
func dictionaryRepresentation() -> NSDictionary
init?(dictionaryRepresentation: NSDictionary?)
}
Ahora, consideremos una estructura ''Película''
struct Movie {
let name: String
let director: String
let releaseYear: Int
}
Permítanme extender la estructura y hacer que se ajuste al protocolo ''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
}
}
}
Básicamente, ahora tenemos una manera de convertir de forma segura una estructura a un diccionario. Digo seguro porque implementamos cómo se forma el diccionario, individual para cada estructura. Mientras la implementación sea correcta, la funcionalidad funcionará. La forma de recuperar la estructura del diccionario es mediante un inicializador que se pueda obtener. Tiene que estar disponible porque la corrupción de archivos y otras razones podrían hacer que la creación de instancias de la estructura de un archivo sea incompleta. Esto nunca puede suceder, pero, es más seguro que sea failable.
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!
}
Los dos métodos anteriores pueden archivar y desarchivar una matriz de estructura ''Película''. Todo lo que debe tener en cuenta es la implementación del protocolo ''Dictionariable'' en cada una de las estructuras que necesita archivar.
Echa un vistazo a este blogpost que escribí comparando tres formas de archivar y desarchivar estructuras swift. Hay una implementación más detallada y un archivo del campo de juego del código explicado anteriormente que puede ejecutar y probar en el enlace.
No estoy seguro de cuál es el problema exacto, pero si usa NSMutableArray en lugar de una matriz Swift, el problema se resuelve:
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")
}
}
Parece ser el caso que cuando unDecoder.decodeObjectForKey devuelve un AnyObject desenvuelto implícitamente, este no se convertirá en una matriz de SquareCoords.
He estado jugando con esto un poco más y me di cuenta de que puede tener algo que ver con el uso de una estructura. (estás creando una matriz de estructuras que son tipos de valor). Esto es un poco de una conjetura pero noté que si un tipo de clase se usa para SquareCoords no hay problema, por ejemplo
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")
}
}
Tal vez la conversión de AnyObject falla a una estructura struct por alguna razón. - Estoy seguro de que alguien más puede proporcionar más información, ¡espero que esto ayude un poco! Swift puede ser tempestuoso: D