Introspection (surtout connaà®tre la classe) via KVC ?

AliGatorAliGator Membre, Modérateur
22:51 modifié dans API AppKit #1
Bonjour,

Je cherche à  connaà®tre la classe d'une variable d'un objet, à  priori par KVC.
Je m'explique, soit la classe suivante :
@interface Dummy : NSObject<br />{<br />&nbsp; NSString* aString;<br />&nbsp; NSDate* aDate;<br />&nbsp; NSInteger anInteger;<br />}<br />@property(nonatomic, retain) NSString* aString;<br />@property(nonatomic, retain) NSDate* aDate;<br />@property(nonatomic, assign) NSInteger anInteger;<br />@end
J'utilise massivement le KVC (car j'en ai plein des classes comme ça et je les remplis depuis du XML, avec un code générique qui utilise donc [tt]setValue:forKey:[/tt]).

Seulement, avant de remplir, j'aimerais éventuellement convertir mes données dans le type attendu. Par exemple si je vois que c'est de type NSDate, je convertis ma NSString "2009-11-09 09:00:00" venant de mon XML en NSDate avant de faire [tt]setValue:forKey:[/tt].
Mieux encore, si ce n'est pas un NSObject mais un NSInteger, l'encapsuler dans un NSNumber pour pouvoir appeler [tt]setValue:forKey:[/tt], etc.



Je ne pense pas que ça puisse être fait directement par KVC, il faut peut-être passer par les méthodes du Runtime (toutes mes variables à  setter par [tt]setValue:forKey:[/tt] ont des @property associées, pour pouvoir les lire via le getter ou via la syntaxe pointée, donc peut-être à  vérifier là -dedans ?), si vous avez des suggestions...

Réponses

  • ClicCoolClicCool Membre
    22:51 modifié #2
    ça manque peut-être d'élégance, mais pourquoi ne pas créer un dictionnaire statique de classe contenant la classe utilisée pour chaque key ?

    Au moins tu retrouveras la classe concernée en KVC ...
  • ClicCoolClicCool Membre
    22:51 modifié #3
    Et, si tu t'en sers beaucoup dans tes projets,y'a sans doutes même moyen de faire une catégorie avec une methode qui "remplisse" ce dictionnaire statique de classe ?.
  • dilarogadilaroga Membre
    22:51 modifié #4
    Avec les méthodes du runtime tu peux avoir la liste des propriétés déclarées dans une classe. Mais je n'ai pas trouvé comment connaitre le type d'une propriété en passant par une méthode prévue à  cet effet. On peut utiliser la chaà®ne de caractères retournée par la méthode property_getAttributes() et l'analyser pour déterminer le type. Cela peut convenir pour les types primitifs que tu pourras encapsuler dans un NSNumber, mais je ne vois pas comment déterminer le type d'un objet (id). Peut etre passer par un property_getName() et se servir du résultat pour appeler un className...
  • ClicCoolClicCool Membre
    novembre 2009 modifié #5
    Si le type de la propriété est '#' c'est un Objet, tu en récupères le nom et, si c'est bien une variable d'instance tu la retrouves avec un [tt]object_getInstanceVariable[/tt] à  laquelle on va demander sa classe ... ?
  • AliGatorAliGator Membre, Modérateur
    22:51 modifié #6
    Bon j'ai commencé avec les méthodes de runtime, et c'est plutôt bien parti.

    J'arrive à  récupérer le type, et à  agir en conséquence.

    Seul problème : l'héritage. En fait dans le cas d'un NSObject, l'encodedType est du genre [tt]@NSString[/tt].
    J'avais commencé en faisant un isEqualToString pour ventiler selon le cas NSString, NSDate, NSNumber, ... et convertir ma chaà®ne en ce type d'objet avec la méthode appropriée selon les cas.

    Le soucis, c'est que si j'ai une variable d'instance de type NSMutableString, je détecte pas que c'est une NSString !
    J'ai bien pensé donc récupérer la Class correspondante, avec [tt]Class cls = objc_getClass(className)[/tt], et ça me retourne bien une Class, mais avec le même nom que celui passé, ce qui est problématique pour les Class Clusters. par exemple ça me retourne une classe "NSString" et pas "NSCFString".
    Du coup (enfin je pense que c'est pour ça), si je lui demande ensuite [tt][cls isKindOfClass:[NSString class]][/tt] il me répond... NO !

    La seule solution que j'ai trouvé c'est de créer une instance de la classe pour tester son type (car init peut renvoyer un type différent de la classe elle-même)... mais je sais pas si c'est top... une autre suggestion ?

    const char* encodingType = property_getAttributes(prop)+1; // zapper le &#39;T&#39; du début<br />if (*encodingType == &#39;@&#39;)<br />{<br />&nbsp; // un NSObject*, extraire sa classe (encodingType ressemble à  @&quot;NSString&quot;)<br />&nbsp; if (*(encodingType+1)==&#39;&quot;&#39;)<br />&nbsp; {<br />&nbsp; &nbsp; const char* closingQuote = strchr(encodingType+2,&#39;&quot;&#39;);<br />&nbsp; &nbsp; NSAssert(closingQuote,@&quot;can&#39;t find closing quote, malformed @encode type?&quot;);<br />&nbsp; &nbsp; NSString* className = [[NSString alloc] initWithCString:(encodingType+2) length:(closingQuote-encodingType-2)];<br />&nbsp; &nbsp; NSLog(@&quot;class name: %@&quot;,className); // &quot;NSString&quot; ou &quot;NSDate&quot;, etc.<br />&nbsp; &nbsp; Class cls = objc_getClass([className cStringUsingEncoding:NSASCIIStringEncoding]);<br />&nbsp; &nbsp; [className release];<br /><br />&nbsp; &nbsp; if ([cls isKindOfClass:[NSString class]])<br />&nbsp; &nbsp; { /* ne marche pas, même si className retourne &quot;NSString&quot; !! o_O */ }<br /><br />&nbsp; &nbsp; id dummyInstance = [[[cls alloc] init] autorelease];<br />&nbsp; &nbsp; if ([dummyInstance isKindOfClass:[NSString class]])<br />&nbsp; &nbsp; { /* là  ça marche, quand className vaut @&quot;NSString&quot; ou @&quot;NSMutableString&quot; */ }<br />&nbsp; } else {<br />&nbsp; &nbsp; // just type &quot;id&quot; without any more info<br />&nbsp; }<br />} // end case &#39;@&#39; / NSObject / id<br />// ...
    
  • AliGatorAliGator Membre, Modérateur
    22:51 modifié #7
    Tiens, comportement étrange :
    NSString* str = [NSString string];<br />BOOL strange = [str isKindOfClass:[NSMutableString class]];<br />// strange vaut... YES !
    
    J'imagine que c'est encore à  cause des class-clusters, puisqu'un NSString ou un NSMutableString est finalement sous le capot de toute façon un NSCFString, même si c'est caché ?
  • mpergandmpergand Membre
    22:51 modifié #8
    NSString* str=@&quot;ok&quot;;<br /><br />NSLog(@&quot;kind of: %d &#092;nmember of: %d&quot;,[str isKindOfClass:[NSMutableString class]],[str isMemberOfClass:[NSMutableString class]]);<br />
    

    kind of: 1
    member of: 0
  • AliGatorAliGator Membre, Modérateur
    22:51 modifié #9
    Ok, mais comment savoir alors si on a une sous-classe de NSMutableString ?
    MyMutableString* mmstr = [MyMutableString string];<br /><br />BOOL subclass = [mmstr isKindOfClass:[NSMutableString class]]; // YES. cool.<br />BOOL exactClass = [mmstr isMemberOfClass:[NSMutableString class]]; // NO. normal.<br /><br />NSString* str = [NSString string];<br />BOOL subclass = [str isKindOfClass:[NSMutableString class]]; // YES. arg.<br />BOOL exactClass = [str isMemberOfClass:[NSMutableString class]]; // NO. normal.<br />
    
    Du coup que ce soit une NSMutableString ou une NSString, ça nous dit YES quand on demande si c'est une sous-classe de NSMutableString... Et qu'on ait une MyMutableString ou une NSString, impossible de faire la différence...

    Bon je m'en suis accomodé, puisque de toute façon je m'en fiche, c'est juste pour savoir si c'est une chaà®ne, et dans ce cas je fais [tt][cls stringWithString:machaine];[/tt] comme ça ça crée une NSString ou une NSMutableString ou une MyMutableString dans tous les cas. C'est juste que je trouvais la réponse YES étrange, faisant croire qu'une NSString est une NSMutableString... Mais j'imagine que c'est à  cause du fait que c'est un class cluster.
  • ClicCoolClicCool Membre
    novembre 2009 modifié #10
    Pour savoir si ta classe est une NSString ou une NSMutableString tu peux utiliser:
    [tt]+ (BOOL) respondsToSelector:@selector(stringWithCapacity:)[/tt] non ?
  • ClicCoolClicCool Membre
    novembre 2009 modifié #11
    Un dernier détail qui lui ne résout pas ton problème puisque tu peux pas faire le test pour toutes les sous-classes éventuelles, mais, par curiosité, si au lieu de [tt]isKindOfClass:[NSString class]][/tt] sur l'instance, tu testais directement l'égalité des classes ?
    if (cls == [NSString class]]) {.../...
    

    J'ai pas testé mais à  priori tu aurais un résultat fiable non ?
    Le même qu'avec [tt]isMemberOfClass[/tt] sur l'instance ?

    [EDIT] mais si! cette approche pourrait résoudre le problème d'éventuelles sous-classe avec la méthode de Classe de NSObject [tt]+ (BOOL)isSubclassOfClass:(Class)aClass[/tt]
    Pas besoin d'instancier la classe pour tester.
    dans 1257777357:
    .../...
    .../...<br />    Class cls = objc_getClass([className cStringUsingEncoding:NSASCIIStringEncoding]);<br />    [className release];<br /><br />    if ([cls isKindOfClass:[NSString class]])<br />    { /* ne marche pas, même si className retourne &quot;NSString&quot; !! o_O */ }<br />.../...
    


    En fait c'est normal qu'un [tt]isKingOfClass[/tt] envoyé à  une classe ne marche pas dans ton code puisque c'est une méthode d'instance non ?

    [EDIT2] Bon ça résout le côté inélégant d'instancier une classe juste pour la tester, après j'ai pas testé et il est possible que la réponse soit aussi surprenant qu'avec [tt]isKindOfClass[/tt] appliqué à  une instance ... ?
  • AliGatorAliGator Membre, Modérateur
    22:51 modifié #12
    Pour tester la mutability avec un respondsToSelector, c'est hors de question, car le but est de faire un truc générique, et non de devoir tester toutes les classes possibles et imaginables..

    Pour l'égalité pure des classes, je ne veux pas non plus, car je veux que ça réponde oui si cls est une sous-classe de celle que je teste, par exemple si l'utilisateur a créé une sous-classe perso.
    S'il a créé une classe MyMutDico, sous-classe de NSMutableDictionary, je veux pouvoir détecter que c'est un NSMutableDictionary, qui est lui-même un NSDictionary.

    Sinon, dans la doc de isKindOfClass, ils disent que si le receiver est une Class, ça marche aussi
    Mais en effet, j'avais loupé [tt]+isSubclassOfClass:[/tt], merci :) Par contre faut espérer que ça fasse pas des misères avec les class clusters, puisque si ma className, obtenue par extraction depuis property_getAttributes ou autre @encode(), est NSString, ça ne revient pas au même de faire le test sur la classe ou sur l'instance (puisque [tt]-[NSString init][/tt] renvoie non pas une NSString mais une NSCFString)

    Bref, pas simple tout ça !
  • ClicCoolClicCool Membre
    novembre 2009 modifié #13
    dans 1257810727:
    .../...
    Sinon, dans la doc de isKindOfClass, ils disent que si le receiver est une Class, ça marche aussi .../...
    ça ne revient pas au même de faire le test sur la classe ou sur l'instance.../..


    en effet la doc dit:
    If the receiver is a class object, this method returns YES if aClass is a Class object of the same type, NO otherwise

    Sauf que dans le cas de Class Cluster elle est encore abstraite puisque non instanciée.

    Du reste la doc dit aussi:
    Be careful when using this method on objects represented by a class cluster.../...
    // DO NOT DO THIS!<br />if ([myArray isKindOfClass:[NSMutableArray class]])<br />{<br />    // Modify the object<br />}
    


    Alors que pour [tt]isSubclassOfClass[/tt] il n'est fait mention d'aucune "special consideration" ce qui laisse espérer un comportement plus transparent...




    dans 1257810727:
    Pour tester la mutability avec un respondsToSelector, c'est hors de question, car le but est de faire un truc générique, et non de devoir tester toutes les classes possibles et imaginables..

    Là , le hors de question est un peu brutal.
    Tu parlais depuis 2 posts de tes difficultés à  différencier une NSString d'une NSMutableString (et leurs éventuelles sous classes respectives).
    T'as pas ici à  tester toutes les classes imaginables mais juste un seul est unique appel à  [tt]respondsToSelector[/tt] auquel la classe comme les sous-classes éventuelles répondront convenablement.
    Returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.

    Je t'accorde bien volontiers que c'est moins beau, mais c'est un "work around" efficace et plus élégant en s'adressant directement à  la classe que dans ta construction instanciant un "dummy".
    Bien sur si tu veux aussi diférencier aussi toutes les autres class Cluster mutable/non mutable ça fait une ligne de code chaque fois pour chaque classe cluster ... mais c'est pas le bout du monde non plus.


    De toutes façons [tt]isKindOfClass[/tt] devrait être la bonne solution élégante ... j'attends tes tests avec impatience.  ;)

    [EDIT] re-formulation plus claire (en italique)
  • AliGatorAliGator Membre, Modérateur
    22:51 modifié #14
    En fait si je parlais de NSMutableString dans tous mes posts plus haut, c'était pour garder le même exemple. En réalité ce qui m'intéresse c'est de savoir si un meta-objet Class représente soit une classe c, soit une de ses sous classes. Donc par exemple si cls est une NSString ou une de ses sous-classes comme NSMutableString. Et j'ai pris NSString comme exemple exprès car c'est un class cluster, cas spécial qu'il faut aussi prendre en considération ;)

    Bon ceci dit, je met en demi-pause cette partie du projet car il faut que j'avance sur d'autres points plus urgents (et plus "visuels" pour le client) et pas que je m'attarde trop sur ce point. J'y reviendrais peut-être plus tard, mais malheureusement là  je suis plutôt obligé de faire du "quick & dirty" (ce que je déteste, étant perfectionnister ;D) qu'une architecture propre et générique, mais bon, client difficile, faut bien s'adapter :)
  • ClicCoolClicCool Membre
    22:51 modifié #15
    dans 1257843802:
    .../...J'y reviendrais peut-être plus tard, mais malheureusement là  je suis plutôt obligé de faire du "quick & dirty" (ce que je déteste, étant perfectionnister ;D) qu'une architecture propre et générique, mais bon, client difficile, faut bien s'adapter :)


    Je compatis, je connais ça dans ma branche aussi, mais je suis obligé de reconnaitre qu'il faut réellement parfois épargner "au client" le temps qui serait nécessaire à  faire de l'élégant si on peut lui fournir rapidement une solution efficace (et solide dans le temps tout de même)
    Y'a un moment où la beauté de l'art de nos métiers doit s'effacer devant les besoins réels de "nos clients", voire même un moment où ce perfectionisme peut lui nuire.
  • dilarogadilaroga Membre
    22:51 modifié #16
    Du code qui évite d'instancier quoique ce soit et de déterminer le type de chacune des propriétés.... ça peut, peut être aider...

    <br />//	NSLog(@&quot;Superclass: %s&quot;, class_getName(class_getSuperclass([Foo class]))) ;<br />	NSUInteger count;<br />	Ivar *vars	= class_copyIvarList([Foo class], &amp;count) ; // récupère les ivars<br /><br />&nbsp; &nbsp; &nbsp; &nbsp; // class_copyPropertyList() récupère uniquement les propriétés d&#39;où le cast plus bas... <br />&nbsp; &nbsp; &nbsp; &nbsp; // c&#39;est pas tres beau mais sinon faut tester si l&#39;ivar courante est une propriété....<br />//	objc_property_t *vars = class_copyPropertyList([NSString class], &amp;count) ;<br />&nbsp; &nbsp; &nbsp; &nbsp; for (NSUInteger i=0; i&lt;count; i++)<br />	{<br />		Ivar var	= (Ivar)vars[i] ;<br />		NSLog(@&quot;%s %s&quot;, ivar_getName(var), ivar_getTypeEncoding(var)) ;<br />	}<br />
    
  • AliGatorAliGator Membre, Modérateur
    22:51 modifié #17
    Merci bien ;D
    Mais le truc, c'est qu'en fait comme ça que je faisait au début, mais ça ne résout pas le pb des class clusters, qui m'imposent d'instancier pour connaà®tre la "vraie" classe.
    Par exemple si j'ai une variable d'instance de type NSString, le typeEncoding retourné sera [tt]@NSString...[/tt], dont je peux extraire le nom de la classe "NSString". Mais d'après ce nom de classe je ne peux pas avoir la vraie classe "NSCFString" :(

    D'ailleurs je fais toujours comme ça (et pour rester KVC-Compliant je teste même tous les cas, si ma clé est définie comme une @property, si non et si accessInstanceVariablesDirectly est à  YES pour la classe, alors je teste les Ivars, et récupère leur typeencoding, etc...)

    N'empêche qu'une fois que j'ai le TypeEncoding, j'ai toujours le mm problème, si c'est une class cluster, impossible de connaà®tre la classe sous-jacente utilisée sans instancier...
Connectez-vous ou Inscrivez-vous pour répondre.