[Swift] NSUndoManager modifie les Int

Je me trouve face à  un problème de taille. Pour gérer le undoManager avec Swift j'ai codé une petite extension à  NSObject :



func registerValueToUndoManager(value: AnyObject?, forKey key: String) {
if let undoManager = NSApplication.sharedApplication().keyWindow?.undoManager {
undoManager.prepareWithInvocationTarget(self).setValue(value, forKey: key)
}
}

func registerUndoForSelfWithSelector(sel: Selector, object: AnyObject?) {
if let undoManager = NSApplication.sharedApplication().keyWindow?.undoManager, let object: AnyObject = object {
undoManager.registerUndoWithTarget(self, selector: sel, object: object)
}
}

C'est très pratique et ça fonctionne (presque) correctement. Explications :


Si registerValueToUndoManager fonctionne du tonnerre il n'est utile que pour les propriétés et utilisé dans un didSet.


C'est pourquoi j'ai aussi registerUndoForSelfWithSelector pour utiliser une méthode n'ayant qu'un paramètre. Si ça fonctionne dans la plupart des cas quand on utilise un Int c'est autre chose. Ma méthode est tout le temps appelée avec une valeur fantasque. Enfin pas tant que ça il s'avère que ça fait un truc dans ce genre :



val << 8 || b00110111

Les grandes valeurs sont tronquées et les petites sont augmentées. Et c'est tout le temps le même "algo" qui est utilisé. 0 devient 55, 1 devient 311, etc...


 


Quelqu'un a déjà  vu une telle chose ?


 


Je précise que je suis en Swift 1.2 Xcode 6.3b1


Mots clés:

Réponses

  • AliGatorAliGator Membre, Modérateur

    Int n'est pas un AnyObject (juste un Any) ; c'est sans doute là  qu'est le problème. (d'autant que je me demande si la valeur que tu obtiens je correspond pas à  un Tagged Pointer, ce qui aurait du sens dans ce contexte surtout s'il box automatiquement ton Int dans un NSNumber et que quand ta valeur est petite du coup il utilise un Tagged Pointer pour l'optimisation)


     


    Si tu interprète ta valeur comme étant un NSNumber (après tout le NSUndoManager ne manipule que des objets ou des valeurs boxed dans un NSNumber ou une NSValue) comme d'ailleurs tu le ferais en ObjC, ça donne pas des choses plus cohérentes ?


  • AliGatorAliGator Membre, Modérateur
    février 2015 modifié #3
    Note également que AnyObject fait de l'autoboxing Objective-C dès que tu importes le module Foundation.
     
    Par exemple :

    1) si tu tapes ceci dans un Playground vide, sans importer Foundation :
    let swiftArray: [AnyObject] = ["Hello", 5]
    Alors cela ne va pas compiler, car tu fais alors du pur Swift (tu n'as pas importé le module Foundation ni fait de Bridging Objective-C), et donc swiftArray est sensé être un tableau de AnyObject (autrement dit de reference types, d'instances de classes si tu préfères).
    Or en Swift pur, les types String et Int ("Hello" et 5) sont des value type (des struct, en fait), donc ce n'est pas un AnyObject et le compilateur va te sortir une erreur.

    2) Si tu remplaces dans l'exemple précédent AnyObject par Any, là  le code Swift va compiler, car les types String et Int de Swift sont des struct qui se conforment au type générique Any (mais pas AnyObject).


    3) Par contre si tu importes Foundation avant, alors tu as une implémentation Objective-C, derrière, autrement dit "AnyObject" correspond alors un peu à  "id", et surtout "Hello" sera casté en type NSString, et ton 5 sera automatiquement boxed/encapsulé dans un NSNumber (comme si tu avais écrit @5 en ObjC) :
    import Foundation

    let mixedArray: [AnyObject] = ["Hello", 5]

    mixedArray[0].dynamicType // Swift._NSCountinuousStirng, le bridging type pour NSString
    mixedArray[1].dynamicType // __NSCFNumber, le bridging type pour NSNumber


    ---



    Comme tu le vois, si tu n'importes pas Foundation et que tu fais du Swift pur, AnyObject ne peut représenter que des classes, et une String ou un Int swift n'en font pas partie...

    MAIS dès que tu importes Foundation, alors les types NSString et NSNumber, qui eux sont conformes à  AnyObject, permettent tout à  coup un bridging Objective-C implicite et changent la donne. Et après tout si tu utilises un NSUndoManager c'est que tu bridges ton code Swift avec de l'Objective-C... et en Objective-C, NSUndoManager box déjà  lui-aussi les valeurs dans des NSNumber ou NSValue, donc pas étonnant qu'en Swift la même chose se passe aussi !


    ---


    Tout ça pour dire qu'à  mon avis, si tu interprètes ton Int dont tu parles plus haut dans ton code comme un NSNumber (car c'en est sûrement un, ton Int ayant été automatiquement boxé en NSNumber), et demande d'en extraire sa intValue, tu devrais retomber sur tes pas...
  • Merci pour toutes ces explications ! J'avais mal compris le bridging visiblement. 

     

    Comme j'ai modifié le code qu'il fonctionne et qu'il est commit je vais éviter de tout casser et je vais tester sur un autre truc que j'ai à  implémenter. 

     

    Maintenant y'a quand même une subtilité qui me turlupine un peu: tes explications montrent pourquoi le code suivant ne fonctionne pas correctement :



    func registerUndoForSelfWithSelector(sel: Selector, object: AnyObject?) {
    if let undoManager = NSApplication.sharedApplication().keyWindow?.undoManager, let object: AnyObject = object {
    undoManager.registerUndoWithTarget(self, selector: sel, object: object)
    }
    }

     

    Mais cela n'explique pas pourquoi ce code fonctionne :



    func registerValueToUndoManager(value: AnyObject?, forKey key: String) {
    if let undoManager = NSApplication.sharedApplication().keyWindow?.undoManager {
    undoManager.prepareWithInvocationTarget(self).setValue(value, forKey: key)
    }
    }

    Le setValue:forKey: doit certainement y être pour quelque chose j'imagine...


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