Array, Dictionary et NSCoding

Joanna CarterJoanna Carter Membre, Modérateur
décembre 2014 modifié dans Objective-C, Swift, C, C++ #1

Bonjour à  toutes et à  tous


 


Je viens d'experimenter avec Swift, en refaisant plusieurs de mes classes du dernier projet.


 


Parmi ces classes, j'ai besoin d'un OrderedDictionary, donc je l'ai bidouillé avec quelques exemples et j'ai abouti avec ce code :



struct OrderedDictionary<keyType : protocol<Hashable, Printable>, valueType>
{
var keys: Array<keyType> = []

var values: Dictionary<keyType, valueType> = [:]

subscript(key: keyType) -> valueType?
{
get
{
return self.values[key]
}
set(newValue)
{
if newValue == nil
{
if let index = find(keys, key)
{
self.keys.removeAtIndex(index)

self.values.removeValueForKey(key)
}

return
}

let oldValue = self.values.updateValue(newValue!, forKey:key)

if oldValue == nil
{
self.keys.append(key)
}
}
}

subscript(index: Int) -> valueType?
{
get
{
if (index < 0) || (index >= keys.endIndex)
{
return nil
}

let key = keys[index]

return values[key]!
}
}

init() {}

var description: String
{
var result = "{\n"

for i in 0 ..< self.keys.count
{
let key = self.keys[i]

result += "[\(i)]: \(key) => \(self[key])\n"
}

result += "}"

return result
}
}

Bon ! mais l'OrderedDictionary fait partie d'une classe qui doit être archivé comme partie d'une hiérarchie d'objets.


 


Mais OrderedDictionary est un struct et, dans cette hiérarchie, keyType et valueType sont les enums ; et on ne peut archiver que les classes marquées comme @objc ou qui héritent de NSObject.


 


Après beaucoup de recherches, j'ai trouvé que le code suivant, dans la classe qui contient le dictionnaire, va assez bien.



required convenience init(coder aDecoder: NSCoder)
{
self.init()

let keys = aDecoder.decodeObjectForKey("availableFuelTypeKeys") as NSArray

let values = aDecoder.decodeObjectForKey("availableFuelTypeValues") as NSDictionary

for keyItem in keys
{
let key = keyItem as NSNumber

let fuelType = FuelType(rawValue: key.integerValue)

availableFuelTypes.keys.append(fuelType!)

let value = values[key] as NSNumber

let availability = ServiceAvailability(rawValue: value.integerValue)

availableFuelTypes.values[fuelType!] = availability
}
}

func encodeWithCoder(aCoder: NSCoder)
{
let keys: NSMutableArray = NSMutableArray(capacity: availableFuelTypes.keys.count)

for key in availableFuelTypes.keys
{
keys.addObject(key.rawValue)
}

let values: NSMutableDictionary = NSMutableDictionary(capacity: availableFuelTypes.values.count)

for (key, value) in availableFuelTypes.values
{
values[key.rawValue] = value.rawValue
}

aCoder.encodeObject(keys, forKey: "availableFuelTypeKeys")

aCoder.encodeObject(values, forKey: "availableFuelTypeValues")
}

C'est loin d'être joli - je voudrais vous demander si vous avez d'autres idées ?


Réponses

  • Alors pour rendre les types Swift plus facile à  intégrer à  Cocoa j'utilise ce genre d'implémentation. Tout ça vient de mon expérience et ça peut servir dans bien des cas. J'ai fait un code complet d'exemple :



    import Cocoa

    enum ObjectType: Int {
    case None = 0
    case First
    case Second
    case Third
    case Last
    }

    extension ObjectType: NilLiteralConvertible {
    init(nilLiteral: ()) {
    self = .None
    }
    }

    class DummyClass: NSObject, NSCoding, NSCopying {
    var type: ObjectType
    var name: String
    var number: Int

    override init() {
    self.type = .First
    self.name = "Jean-Gilbert"
    self.number = 80085
    super.init() // Facultatif mais un bon réflexe à  avoir
    }

    // NSCoding protocol
    required init(coder aDecoder: NSCoder) {
    self.type = ObjectType(rawValue: aDecoder.decodeObjectForKey("type") as ObjectType.RawValue)!
    self.name = aDecoder.decodeObjectForKey("name") as String
    self.number = aDecoder.decodeIntegerForKey("number")
    super.init() //super.init(coder: ...) si la superclass n'est pas NSObject.
    }
    func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(self.valueForKey("type"), forKey: "type")
    // Ou aCoder.encodeObject(self.type.rawValue, forKey: "type")
    // Eviter le encodeInteger meme si ObjectType.RawValue == Int on s'y perd.
    aCoder.encodeObject(self.name, forKey: "name")
    aCoder.encodeInteger(self.number, forKey: "number")
    // super.encodeWithCoder(aCoder: ...) si la superclass n'est pas NSObject.
    }

    // NSCopying protocol
    func copyWithZone(zone: NSZone) -> AnyObject {
    var dummy = DummyClass()
    dummy.type = self.type
    dummy.name = self.name
    dummy.number = self.number
    return dummy
    }

    // La magie du KVC maintenant
    // Il faut utiliser la rawValue, très utile pour les bindings et la serialization.
    override func valueForUndefinedKey(key: String) -> AnyObject? {
    switch key {
    case "type" :
    return self.type.rawValue
    default :
    return super.valueForUndefinedKey(key)
    }
    }
    override func setValue(value: AnyObject?, forUndefinedKey key: String) {
    switch key {
    case "type" :
    if let val = value as? ObjectType.RawValue {
    if let type = ObjectType(rawValue: val) {
    self.type = type
    return
    }
    }
    self.type = nil
    default :
    super.setValue(value, forUndefinedKey: key)
    }
    }
    }

    Dans ton cas qui est plus précis et dans la mesure ou un Array<keyType> ne répond pas au protocol AnyObject on est obligé de le sérialiser autrement, par le biais de NSData. (Ce code n'est pas de moi, je l'ai un peu modifié, l'utilise dans mes projets et il vient de https://gist.github.com/nubbel/5b0a5cb2bf6a2e353061) :



    extension NSData {
    class func encode<T>(var value: T) -> NSData {
    return withUnsafePointer(&value) { p in
    NSData(bytes: p, length: sizeofValue(value))
    }
    }

    class func decode<T>(data: NSData) -> T {
    let pointer = UnsafeMutablePointer<T>.alloc(sizeof(T.Type))
    data.getBytes(pointer)

    return pointer.move()
    }

    func decode<T>() -> T {
    let pointer = UnsafeMutablePointer<T>.alloc(sizeof(T.Type))
    self.getBytes(pointer)

    return pointer.move()
    }
    }

    Normalement avec ça tu devrais avoir assez pour extrapoler et faire un peu tout ce que tu veux faire ;)


     


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