De l'optimisation du -respondsToSelector: dans certains cas

mai 2012 modifié dans Objective-C, Swift, C, C++ #1
Bonjour à  tous,



À force de faire le curieux, j'ai remarqué qu'Apple utilisait des structures contenant des booléen afin de déterminer si un objet répond à  certaines méthodes optionnelles d'un protocol donné.



Jusqu'à  présent, je faisais quelque chose d'assez simple, et que tout le monde fait:
<br />
<br />
@protocol TotoDelegate &lt;NSObject&gt;<br />
// some required methods...<br />
<br />
@optional<br />
// Some optional methods<br />
- (void)toto:(Toto*)toto willDoSomethingWith:(id)anObject;<br />
<br />
- (void)toto:(Toto*)toto didDoSomethingWith:(id)anObject;<br />
<br />
<br />
@end<br />
<br />
<br />
@implementation Toto<br />
<br />
- (void)doSomethingWith:(id)anObject<br />
{<br />
if( [[self delegate] respondsToSelector:@selector(toto:willDoSomethingWith:)])<br />
[[self delegate] toto:self willDoSomethingWith:anObject];<br />
<br />
// Do something<br />
<br />
<br />
if( [[self delegate] respondsToSelector:@selector(toto:didDoSomethingWith:)])<br />
[[self delegate] toto:self didDoSomethingWith:anObject];<br />
}<br />
<br />






J'ai donc tenté de virer ces appels répétitifs en utilisant des entiers (0 ou 1) pour savoir delegate & dataSource répondent à  telles ou telles méthodes optionnelles :


<br />
<br />
[size=3][color=#657B83][font=Menlo]@interface NRGridView : UIScrollView[/font][/color]<br />
[color=#657B83][font=Menlo]{[/font][/color]<br />
[color=#657B83][font=Menlo]	struct {[/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint willDisplayCell:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint willSelectCell:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint didSelectCell:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint didLongPressCell:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint didSelectHeader:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]	} _gridViewDelegateRespondsTo;[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]	struct {[/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint numberOfSections:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint titleForHeader:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint viewForHeader:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint heightForHeader:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint widthForHeader:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint titleForFooter:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint viewForFooter:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint heightForFooter:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]		unsignedint widthForFooter:1;  [/font][/color]<br />
[color=#657B83][font=Menlo]	} _gridViewDataSourceRespondsTo;[/font][/color]<br />
[color=#657B83][font=Menlo]}[/font][/color]<br />
<br />
[color=#D43F82][font=Menlo]@property (nonatomic, assign) IBOutlet id&lt;NRGridViewDelegate&gt; delegate;[/font][/color]<br />
[color=#D43F82][font=Menlo]@property (nonatomic, assign) IBOutlet id&lt;NRGridViewDataSource&gt; dataSource;[/font][/color]<br />
<br />
<br />
<br />
<br />
[font=Menlo][color=#657b83]@end[/color][/font]<br />
<br />
[font=Menlo]@implementation NRGridView[/font]<br />
<br />
@synthesize dataSource = _dataSource;<br />
@dynamic delegate;<br />
<br />
<br />
[color=#657B83][font=Menlo]- ([color=#d43f82]void[/color])setDelegate:([color=#d43f82]id[/color]&lt;NRGridViewDelegate&gt;)delegate[/font][/color]<br />
[color=#657B83][font=Menlo]{[/font][/color]<br />
[color=#657B83][font=Menlo]	[[color=#d43f82]super[/color] setDelegate:delegate];[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]	_gridViewDelegateRespondsTo.willDisplayCell = [delegate respondsToSelector:[color=#d43f82]@selector[/color](gridView:willDisplayCell:atIndexPath:)];[/font][/color]<br />
[color=#657B83][font=Menlo]	_gridViewDelegateRespondsTo.willSelectCell = [delegate respondsToSelector:[color=#d43f82]@selector[/color](gridView:willSelectCellAtIndexPath:)];[/font][/color]<br />
[color=#657B83][font=Menlo]	_gridViewDelegateRespondsTo.didSelectCell = [delegate respondsToSelector:[color=#d43f82]@selector[/color](gridView:didSelectCellAtIndexPath:)];[/font][/color]<br />
[color=#657B83][font=Menlo]	_gridViewDelegateRespondsTo.didLongPressCell = [delegate respondsToSelector:[color=#d43f82]@selector[/color](gridView:didLongPressCellAtIndexPath:)];[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]	_gridViewDelegateRespondsTo.didSelectHeader = [delegate respondsToSelector:[color=#d43f82]@selector[/color](gridView:didSelectHeaderForSection:)];[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]}[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]- ([color=#d43f82]void[/color])setDataSource:([color=#d43f82]id[/color]&lt;NRGridViewDataSource&gt;)dataSource[/font][/color]<br />
[color=#657B83][font=Menlo]{[/font][/color]<br />
[color=#657B83][font=Menlo][color=#d43f82]if[/color](_dataSource &#33;= dataSource)[/font][/color]<br />
[color=#657B83][font=Menlo]	{[/font][/color]<br />
[color=#657B83][font=Menlo]		[[color=#d43f82]self[/color] willChangeValueForKey:[color=#e41c10]@&quot;dataSource&quot;[/color]];[/font][/color]<br />
[color=#657B83][font=Menlo]		_dataSource = dataSource;[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.numberOfSections = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](numberOfSectionsInGridView:)];[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.titleForHeader = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:titleForHeaderInSection:)];[/font][/color]<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.viewForHeader = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:viewForHeaderInSection:)];[/font][/color]<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.heightForHeader = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:heightForHeaderInSection:)];[/font][/color]<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.widthForHeader = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:heightForHeaderInSection:)];[/font][/color]<br />
<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.titleForFooter = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:titleForFooterInSection:)];[/font][/color]<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.viewForFooter = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:viewForFooterInSection:)];[/font][/color]<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.heightForFooter = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:heightForFooterInSection:)];[/font][/color]<br />
[color=#657B83][font=Menlo]		_gridViewDataSourceRespondsTo.widthForFooter = [dataSource respondsToSelector:[color=#d43f82]@selector[/color](gridView:widthForFooterInSection:)];[/font][/color]<br />
<br />
<br />
[color=#657B83][font=Menlo]		[[color=#d43f82]self[/color] didChangeValueForKey:[color=#e41c10]@&quot;dataSource&quot;[/color]];[/font][/color]<br />
[color=#657B83][font=Menlo]	}[/font][/color]<br />
[color=#657B83][font=Menlo]}[/font][/color][/size]<br />
<br />
<br />
[size=3][color=#657B83][font=Menlo]@end[/font][/color][/size]<br />
<br />




Au final, c'est assez satisfaisant. 2.5x plus rapide au moment du -reloadData sur un 3GS.

Bien entendu, je pense que cette façon de faire n'a de sens que dans ce genre de composant, où des appels récursifs sont nécessaires.

Il y a plusieurs façon d'optimiser, mais c'est de loin celle que je préfère pour ce genre de cas.





Si je reprend le premier exemple mais en utilisant une struct ça donne:
<br />
@implementation Toto<br />
<br />
- (void)doSomethingWith:(id)anObject<br />
{<br />
if( _totoDelegateRespondsTo.willDoSomethingWith)<br />
[[self delegate] toto:self willDoSomethingWith:anObject];<br />
<br />
// Do something<br />
<br />
<br />
if( _totoDelegateRespondsTo.didDoSomethingWith)<br />
[[self delegate] toto:self didDoSomethingWith:anObject];<br />
}<br />






Maintenant j'aimerai vraiment comprendre clairement (j'ai quelques petites idées) pourquoi il serait plus efficace de déclarer un unsigned int n'utilisant qu'un seul octet plutôt qu'un type BOOL directement ?





Merci image/smile.png' class='bbc_emoticon' alt=':)' />

Réponses

  • zoczoc Membre
    'ldesroziers' a écrit:


    Maintenant j'aimerai vraiment comprendre clairement (j'ai quelques petites idées) pourquoi il serait plus efficace de déclarer un unsigned int n'utilisant qu'un seul octet plutôt qu'un type BOOL directement ?


    Non, le code ne déclare pas des unsigned int sur un seul octet, mais des unsigned int sur 1 bit. Ils ne peuvent du coup que prendre la valeur 0 ou 1, ce qui est bien suffisant dans le cas qui nous intéresse.



    Les structs que tu as dans ton code, ce sont des champs de bits (bitsets). L'avantage ? Principalement un gain d'espace mémoire, puisque tu ne vas utiliser qu'un bit pour chaque valeur booléenne, alors qu'avec une variable de type BOOL, tu vas utiliser 32 bits (taille d'un int), donc 32 fois plus...



    Par contre, attention, pour des questions de performance d'accès, le compilateur aligne (sur 32 bits, voir 64) les données en mémoire. Du coup, on pourrait s'attendre à  ce que la taille de la structure "_gridViewDelegateRespondsTo" fasse 1 octet, mais en pratique elle en fera 4 pour conserver l'alignement.



    C'est toujours mieux que son équivalent à  base de BOOL, qui, elle, ferait 20 octets (un BOOL est dans les fait un unsigned int).
  • AliGatorAliGator Membre, Modérateur
    Oui je confirme ce que dit zoc j'allais faire la même réponse... d'ailleurs je n'ai jamais compris pourquoi historiquement un bool n'était pas un unsigned char plutôt qu'un unsigned int ?
  • D'après objc.h
    typedef signed char BOOL;
  • mai 2012 modifié #5
    image/implore.gif' class='bbc_emoticon' alt=' o:) ' /> Merci pour cette explication très claire Zoc !

    Du coup sur un gros projet comme iOS, ça doit être vachement avantageux.
  • zoczoc Membre
    'Thibaut' a écrit:


    D'après objc.h
    typedef signed char BOOL;



    Ca, en pratique, ça va changer la taille de la structure à  base de BOOL, qui ferait du coup 8 octets (5 pour les "vraies" valeurs, +3 pour avoir un multiple de 4). C'est toujours plus que la même à  base de bitsets.



    PS: J'ai simplifié au maximum ces histoires d'alignement, en réalité c'est assez complexe et dépend du contenu de la structure. L'article de Wikipedia donne des exemples assez parlants.
  • CéroceCéroce Membre, Modérateur
    'AliGator' a écrit:


    Oui je confirme ce que dit zoc j'allais faire la même réponse... d'ailleurs je n'ai jamais compris pourquoi historiquement un bool n'était pas un unsigned char plutôt qu'un unsigned int ?


    C'est sans doute un poil plus rapide sur la plupart des microprocesseurs, le type int ayant la taille de la largeur du bus de données. Les instructions de traitement fonctionnant toutes sur cette taille naturelle, et les registres de données ayant également cette taille, il faudrait sans doute convertir l'octet, ce qui peut prendre plus de temps.
  • Pour compléter le sujet, j'aimerai revenir sur les 2 exemples mais avec une boucle histoire de montrer le réel avantage.





    [font=arial,helvetica,sans-serif]
    [/font]<br />
    [font=arial,helvetica,sans-serif][size=3]-[/size][size=3] [/size][size=3]([/size][size=3]void[/size][size=3])[/size][size=3]doSomethingWith[/size][size=3]<img src='http://forum.cocoacafe.fr/public/style_emoticons/<#EMO_DIR#>/sad.png' class='bbc_emoticon' alt=':(' />[/size][size=3]NSArray*[/size][size=3])[/size][size=3]objects[/size]<br />
    [size=3]{[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]for (id anObject in objects)[/size][/font]<br />
    [font=arial,helvetica,sans-serif][size=3]{[/size][/font]<br />
    [font=arial,helvetica,sans-serif]if[size=3]([/size][size=3] [/size][size=3][[[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] respondsToSelector[/size][size=3]:[/size][size=3]@selector[/size][size=3]([/size][size=3]toto[/size][size=3]:[/size][size=3]willDoSomethingWith[/size][size=3]<img src='http://forum.cocoacafe.fr/public/style_emoticons/<#EMO_DIR#>/smile.png' class='bbc_emoticon' alt=':)' />])[/size][/font]<br />
    [font=arial,helvetica,sans-serif][size=3][[[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] toto[/size][size=3]:[/size][size=3]self[/size][size=3] willDoSomethingWith[/size][size=3]:[/size][size=3]anObject[/size][size=3]];[/size][/font]<br />
    <br />
    [font=arial,helvetica,sans-serif][size=3]// Do something[/size]<br />
    [size=3]      if[/size][size=3]([/size][size=3] [/size][size=3][[[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] respondsToSelector[/size][size=3]:[/size][size=3]@selector[/size][size=3]([/size][size=3]toto[/size][size=3]:[/size][size=3]didDoSomethingWith[/size][size=3]<img src='http://forum.cocoacafe.fr/public/style_emoticons/<#EMO_DIR#>/smile.png' class='bbc_emoticon' alt=':)' />])[/size]<br />
    [size=3][[[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] toto[/size][size=3]:[/size][size=3]self[/size][size=3] didDoSomethingWith[/size][size=3]:[/size][size=3]anObject[/size][size=3]];[/size][/font]<br />
    [font=arial,helvetica,sans-serif][size=3]}[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]}[/size][/font]<br />
    [font=arial,helvetica,sans-serif][size=3]
    [/size][/font]









    [font=arial, helvetica, sans-serif]
    [/font]<br />
    [font=arial, helvetica, sans-serif][size=3]-[/size][size=3] [/size][size=3]([/size][size=3]void[/size][size=3])[/size][size=3]doSomethingWith[/size][size=3]<img src='http://forum.cocoacafe.fr/public/style_emoticons/<#EMO_DIR#>/sad.png' class='bbc_emoticon' alt=':(' />[/size][size=3]NSArray*[/size][size=3])[/size][size=3]objects[/size]<br />
    [size=3]{[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]for (id anObject in objects)[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]{[/size][/font]<br />
    [font=arial, helvetica, sans-serif]if[size=3]([/size][size=3] _totoDelegateRespondsTo.willDoSomethingWith[/size][size=3])[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3][[[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] toto[/size][size=3]:[/size][size=3]self[/size][size=3] willDoSomethingWith[/size][size=3]:[/size][size=3]anObject[/size][size=3]];[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]// Do something[/size]<br />
    [size=3]       if[/size][size=3]([/size][size=3] [/size][/font][font=arial, helvetica, sans-serif][size=3]_totoDelegateRespondsTo.didDoSomethingWith[/size][/font][font=arial, helvetica, sans-serif][size=3])[/size]<br />
    [size=3][[[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] toto[/size][size=3]:[/size][size=3]self[/size][size=3] didDoSomethingWith[/size][size=3]:[/size][size=3]anObject[/size][size=3]];[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]}[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]}[/size][/font]<br />
    <br />
    [font=arial, helvetica, sans-serif][size=3]
    [/size][/font]











    Il y aussi une 3ème solution qui permet de conserver l'utilisation de -respondsToSelector: à  l'instant T (ici, avant le début de la fastenum sur 'objects') mais je vais aussi vous montrer qu'elle n'est pas forcément mieux dans certains cas.











    [font=arial, helvetica, sans-serif]
    [/font]<br />
    [font=arial, helvetica, sans-serif][size=3]-[/size][size=3] [/size][size=3]([/size][size=3]void[/size][size=3])[/size][size=3]doSomethingWith[/size][size=3]<img src='http://forum.cocoacafe.fr/public/style_emoticons/<#EMO_DIR#>/sad.png' class='bbc_emoticon' alt=':(' />[/size][size=3]NSArray*[/size][size=3])[/size][size=3]objects[/size]<br />
    [size=3]{[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]BOOL respondsToWillDoSomethingWith = [/size][/font][font=arial, helvetica, sans-serif][size=3][[[/size][/font][font=arial, helvetica, sans-serif][size=3]self[/size][/font][font=arial, helvetica, sans-serif][size=3] [/size][/font][font=arial, helvetica, sans-serif][size=3]delegate[/size][/font][font=arial, helvetica, sans-serif][size=3]][/size][/font][font=arial, helvetica, sans-serif][size=3] respondsToSelector[/size][/font][font=arial, helvetica, sans-serif][size=3]:[/size][/font][font=arial, helvetica, sans-serif][size=3]@selector[/size][/font][font=arial, helvetica, sans-serif][size=3]([/size][/font][font=arial, helvetica, sans-serif][size=3]toto[/size][/font][font=arial, helvetica, sans-serif][size=3]:[/size][/font][font=arial, helvetica, sans-serif][size=3]willDoSomethingWith[/size][/font][font=arial, helvetica, sans-serif][size=3]<img src='http://forum.cocoacafe.fr/public/style_emoticons/<#EMO_DIR#>/smile.png' class='bbc_emoticon' alt=':)' />][/size][/font][font=arial, helvetica, sans-serif][size=3];[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]BOOL respondsToDidDoSomethingWith = [/size][/font][font=arial, helvetica, sans-serif][size=3][[[/size][/font][font=arial, helvetica, sans-serif][size=3]self[/size][/font][font=arial, helvetica, sans-serif][size=3] [/size][/font][font=arial, helvetica, sans-serif][size=3]delegate[/size][/font][font=arial, helvetica, sans-serif][size=3]][/size][/font][font=arial, helvetica, sans-serif][size=3] respondsToSelector[/size][/font][font=arial, helvetica, sans-serif][size=3]:[/size][/font][font=arial, helvetica, sans-serif][size=3]@selector[/size][/font][font=arial, helvetica, sans-serif][size=3]([/size][/font][font=arial, helvetica, sans-serif][size=3]toto[/size][/font][font=arial, helvetica, sans-serif][size=3]:did[/size][/font][font=arial, helvetica, sans-serif][size=3]DoSomethingWith[/size][/font][font=arial, helvetica, sans-serif][size=3]<img src='http://forum.cocoacafe.fr/public/style_emoticons/<#EMO_DIR#>/smile.png' class='bbc_emoticon' alt=':)' />][/size][/font][font=arial, helvetica, sans-serif][size=3];[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]for (id anObject in objects)[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]{[/size][/font]<br />
    [font=arial, helvetica, sans-serif]if[size=3]([/size][size=3] respondsToWillDoSomethingWith[/size][size=3])[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3][[[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] toto[/size][size=3]:[/size][size=3]self[/size][size=3] willDoSomethingWith[/size][size=3]:[/size][size=3]anObject[/size][size=3]];[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]  // Do something[/size]<br />
    [size=3]         if[/size][size=3]([/size][size=3] [/size][/font][font=arial, helvetica, sans-serif][size=3]respondsToDidDoSomethingWith[/size][/font][font=arial, helvetica, sans-serif][size=3])[/size]<br />
    [size=3][[/size][size=3]self[/size][size=3] [/size][size=3]delegate[/size][size=3]][/size][size=3] toto[/size][size=3]:[/size][size=3]self[/size][size=3] didDoSomethingWith[/size][size=3]:[/size][size=3]anObject[/size][size=3]];[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]  }[/size][/font]<br />
    [font=arial, helvetica, sans-serif][size=3]}[/size][/font]<br />
    <br />
    [font=arial, helvetica, sans-serif][size=3]
    [/size][/font]





    J'utilisais cette "optimisation" dans le layoutSubviews de ma gridView, afin de savoir si le delegate répond à  "gridView:willDisplayCellAtIndexPath:"

    Du coup ça donnait la même chose, avant d'entre dans la boucle.



    Sauf que.. layoutSubviews est appelé systématiquement à  chaque scroll... Donc l'utilisation de la structure "delegateRespondsTo" est encore une fois mieux que la 3eme solution ci-dessus.





    Voilà  voilà . Après je tiens à  préciser que lorsque j'ai parlé de gain de rapidité de l'ordre de 2.5x sur un 3GS, je parle millisecondes.

    0.00645xxxx secondes contre 0.0158xxxx auparavant.
  • BaardeBaarde Membre
    Petite précision sur le type BOOL :



    BOOL est défini comme un entier signé sur un octet. Je crois qu'il n'y aurait aucun avantage à  le stocker sur 32 bits, même en terme de performance : aucune conversion n'est nécessaire, le processeur pouvant très bien travailler l'octet de poids faible du registre en ignorant les 3 autres.



    Par contre je ne sais pas pourquoi BOOL est signé. Ceci a d'ailleurs l'inconvénient d'empêcher son utilisation dans un bitset. En effet, un BOOL stocké sur un bit ne peut prendre que deux valeurs : 0 et -1, raison pour laquelle on préfère utiliser un unsigned int.
  • FKDEVFKDEV Membre
    juillet 2012 modifié #10
    C'est vraiment une optimisation facile et intéressante. Surtout quand il y a des boucles potentiellement longue.

    Cela fonctionne bien dans le cas de NRGridView.



    Du coup je me dis que si, un jour, je me retrouvais dans la situation d'avoir à  traiter de la même manière de nombreux objets de types potentiellement différents (par exemple des annotations sur une carte), je mettrais en place un protocole du type :


    <br />
    @protocol Toto<br />
    <br />
    -(BOOL)hasMethodTiti;<br />
    -(BOOL)hasMethodTata;<br />
    <br />
    @optional<br />
    -(void)MethodTiti;<br />
    -(void)MethodTata;<br />
    @end<br />




    Est-ce que vous pensez que l'on gagnerait beaucoup de temps à  remplacer un appel à  [font=courier new,courier,monospace]respondsToSelector[/font] par un appel à  une méthode du type :
    <br />
    -(BOOL)hasMethodTiti<br />
    {<br />
    	  return YES;<br />
    }<br />




    Bien sûr, pour une API, c'est un peu moche d'obliger la classe appelée à  coder ça, mais en interne sur un projet c'est acceptable.
  • Que du temps soit gagné ou pas, au final c'est le développeur qui en perd. Quelle idée de devoir se conformer à  des méthodes qui permettent de savoir si on se conforme bien à  des méthodes optionnelles... Je trouve ça juste tordu image/biggrin.png' class='bbc_emoticon' alt=':D' />

    Même en interne, je trouve ça inacceptable.
  • AliGatorAliGator Membre, Modérateur
    Oui je suis d'accord c'est une approche étrange (et mauvaise) que d'avoir à  demander à  celui qui implémente le protocole de dire, en plus en implémentant une méthode @required lui-même, s'il répond à  une méthode @optional plus loin. Tordu et dangereux.



    Mieux vaut avoir des flags en interne. Le bitfield a de très nombreux avantages :

    - Pas géré par celui qui implémente le protocole, mais bien celui qui implémente la classe et appelle le protocole, pour éviter en interne des respondsToSelector, tout en continuant de calculer dynamiquement s'il répond ou non au lieu de laisser l'utilisateur du framework le dire

    - Rapide, bien plus que d'appeler une méthode Objective-C comme -(BOOL)hasMethodTiti, qui déjà  serait plus longue qu'appeler une méthode C "BOOL hasMethodTiti()", elle-même encore plus longue qu'un simple lookup de variable comme le fait un bitfield.


  • Quelle idée de devoir se conformer à  des méthodes qui permettent de savoir si on se conforme bien à  des méthodes optionnelles... Je trouve ça juste tordu image/biggrin.png' class='bbc_emoticon' alt=':D' />

    Même en interne, je trouve ça inacceptable.





    Oui je suis d'accord c'est une approche étrange (et mauvaise) que d'avoir à  demander à  celui qui implémente le protocole de dire, en plus en implémentant une méthode @required lui-même, s'il répond à  une méthode @optional plus loin. Tordu et dangereux.




    inacceptable.

    une approche étrange (et mauvaise)

    Tordu et dangereux.



    Quels sont vos arguments exactement ?



    On est d'accord que NSObject est d'abord un protocol qui défini des interfaces @required tel que respondsToSelector.

    http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html



    Donc dans les fait, respondsToSelector est bien une des "méthodes qui permettent de savoir si on se conforme bien à  des méthodes optionnelles" ou bien une manière de "demander à  celui qui implémente le protocole de dire, en plus en implémentant une méthode @required lui-même, s'il répond à  une méthode @optional plus loin".

    D'ailleurs c'est bien la base du principe des protocol (qu'on appelle aussi interface dans d'autres langages). Ce sont des méthodes dont on ne peut pas être sûr au moment de la compilation et du link si elles vont être bien présentes au moment du runtime. Donc il est obligatoire d'avoir un système permettant à  l'objet qui implémente le protocol ou l'interface de signaler quelles sont les méthodes ou interfaces qu'il implémente.

    Dans le système COM de Microsoft, c'est le terrible QueryInterface. En Objective C/Cocoa, c'est respondsToSelector.



    Relisez-vous, vous êtes en train de dire que respondsToSelector est tordu, inacceptable, dangereux et mauvais.





    Là -dessus, moi je dis juste qu'au lieu d'avoir une seule méthode d'introspection pourquoi, on ne pourrait pas mettre en place une convention qui impose de créer une méthode d'introspection par méthode optionnelle. Si ce ne serait pas plus efficace dans certains cas. J'essaye juste d'étendre la réflexion qui est, si j'ai bien compris, comment faire plus efficace qu'un appel à  respondsToSelector.



    Ce que je propose, c'est juste remplacer respondsToSelector:(SEL) par respondsToSelectorTiti et respondsToSelectorToto.

    Je ne vois pas en quoi c'est tordu ou hérétique.

    Think Different ça vous dit quelque chose ?



    C'est un peu gonflant à  force, ces gens qui pensent toujours détenir la vérité ultime sur la bonne manière de faire (et qui en plus sont désagréables).



    Vous savez quand même que la bonne manière de faire est toujours relative au problème auquel on fait face.

    Entre ce que vous décrivez et ce que je décris, la différence est si faible que vous devez bien imaginer qu'il y a des cas où cela peut coller, non ?



    Maintenir un bitfield, du côté de celui qui utilise l'objet qui implémente le protocol, c'est bien, j'approuve.

    Mais vous imaginez bien que si cet objet est connecté à  10000 objets qui implémentent le protocol alors ça va pas coller. Si ces 10000 objets appartiennent en fait à  20 classes différentes, il vaut mieux coder 20 méthodes hasTiti que stocker 10000 bitfields.

    (Si on admet qu'il n'y a pas de création de méthodes dynamiquement).
  • AliGatorAliGator Membre, Modérateur
    Heu ton raisonnement est totalement décalé avec ce que l'on a dit.



    1) "respondsToSelector" est une méthode que l'utilisateur n'a pas à  implémenter pour dire si oui ou non l'objet répond à  tel ou tel sélecteur.

    Alors que déclarer un @protocol où tu aurais une méthode "hasMethodTiti" en @required, ça obligerait la personne qui veut créer une classe se conformant à  ce protocole à  implémenter sa propre version de hasMethodTiti. Et la personne peut alors l'implémenter un peu n'importe comment, ce qui déjà  n'est pas top, mais en plus est obligée de l'implémenter, donc d'écrire des lignes de code, une pour chaque méthode @optional de son protocole dans le cas que tu expliques. Soit en faisant directement un "return YES;" ou un "return NO;", soit en faisant un appel à  "[self respondsToSelector:@selector(titi)];" ce qui fait qu'on aurait une méthode qui appelle respondsToSelector, autant l'appeler directement nous même, surtout qu'on parle là  d'optimisation au runtime, donc certainement pas de rajouter une indirection d'appel qui au contraire ralentirait les choses



    2) La différence c'est que respondsToSelector est bien définie dans le @protocol NSObject, mais il se trouve que ce protocol est adopté par la classe NSObject. Du coup tu n'as pas à  redéfinir l'implémentation de respondsToSelector dans chacune de tes classes. Et encore heureux !





    En fait le plus gênant dans ton approche de créer un @protocol avec autant de méthodes @required de type "hasMethodTiti" que tu as de méthodes @optional de type "titi", c'est qu'au lieu de garder l'intelligence du côté de la classe manipulant ton protocole, et donc de garder le principe fondamental d'encapsulation propre à  la POO, tu laisses tous ceux qui vont créer des classes implémentant ce protocole (et il peut y en avoir un sacré paquet, celui qui utilise ton framework peut vouloir créer 20 classes se conformant à  ton protocole si ça l'amuse) la responsabilité de dire s'ils implémentent telle ou telle méthode... alors que tu peux déjà  le savoir toi-même par introspection grâce à  respondsToSelector justement.



    Je n'imagine même pas à  quoi ça pourrait ressembler pour des protocoles comme UITableViewDelegate, dont le nombre de méthodes optionnelles est assez conséquent... et pour lesquelles avec ta solution il faudrait autant de méthodes @required (à  lister dans le protocole, et surtout à  implémenter quand on code nos tableviews) pour indiquer si on implémente ou pas telle ou telle méthode ?



    Donc celui qui crée une classe se conformant à  ton protocole non seulement est obligé d'implémenter des méthodes et d'écrire des lignes de code inutiles (car dont tu pourrais te passer, en utilisant plutôt respondsToSelector dans ton framework et en plus garder la responsabilité de la gestion de tout ça plutôt que laisser l'utilisateur risquer de faire n'importe quoi), en plus au lieu de réduire le nombre d'appels Objective-C (ce qui est le but de ce sujet, au départ, en proposant en quelques sortes de mettre en cache dans un bitfield les résultats de respondsToSelector pour réduire les appels) tu ajoutes potentiellement des indirections, et enfin c'est la meilleure manière de risquer des plantages.

    En effet, si l'utilisateur retourne YES à  hasMethodTiti alors qu'il n'implémente pas la méthode titi, ton code va planter.

    Et même si l'utilisateur crée une classe A qui implémente ton protocole, mais n'implémente pas la méthode titi, et implémente hasMethodTiti en lui faisant correctement retourner "NO", bah si quelqu'un ensuite crée une sous-classe B de la classe A et implémente la méthode titi, cela ne va pas marcher du coup.


  • Relisez-vous, vous êtes en train de dire que respondsToSelector est tordu, inacceptable, dangereux et mauvais.


    Non. Cf la réponse d'Aligator.


  • 1) "respondsToSelector" est une méthode que l'utilisateur n'a pas à  implémenter pour dire si oui ou non l'objet répond à  tel ou tel sélecteur.


    C'est un fait. Merci Objective-C et l'introspection.

    Il faut cependant accepter que dans certains langage (C/C++), l'introspection n'existe pas. On doit faire soi-même l'équivalent de "respondsToSelector". Par exemple QueryInterface.



    Le concept de laisser l'utilisateur faire son équivalent de respondsToSelector n'est donc pas une hérésie puisque cela existe dans des langages différents de l'objective-C.

    Je suis d'accord que c'est mieux quand c'est fourni par le langage.

    Je reconnais que c'est "dangereux" ou disons que cela demande une discipline supplémentaire.




    Alors que déclarer un @protocol où tu aurais une méthode "hasMethodTiti" en @required, ça obligerait la personne qui veut créer une classe se conformant à  ce protocole à  implémenter sa propre version de hasMethodTiti.


    En effet. C'est une convention. Un compromis pour obtenir une optimisation dans certains cas de figures.

    J'ai pas dit que TOUS les protocols devaient être fait comme ça.

    J'ai parlé d'un projet interne et de 10000 objets. Merci de prendre en compte le contexte.




    Et la personne peut alors l'implémenter un peu n'importe comment, ce qui déjà  n'est pas top, mais en plus est obligée de l'implémenter, donc d'écrire des lignes de code, une pour chaque méthode @optional de son protocole dans le cas que tu expliques. Soit en faisant directement un "return YES;" ou un "return NO;",


    Oui, on est obligé. On n'a rien sans rien dans la vie.

    Un peu comme on est obligé de faire des release ou d'utiliser des autoreleaseloop dans certains cas.

    Encore une fois pas dans tous les cas.




    soit en faisant un appel à  "[self respondsToSelector:@selector(titi)];"

    ce qui fait qu'on aurait une méthode qui appelle respondsToSelector, autant l'appeler directement nous même, surtout qu'on parle là  d'optimisation au runtime, donc certainement pas de rajouter une indirection d'appel qui au contraire ralentirait les choses


    Ben ça c'est sûr ! On peut aller assez loin dans le délire : "si le gars fait n'importe quoi, ça va faire n'importe quoi".

    On parle de gens raisonnables ou bien encadrés.






    2) La différence c'est que respondsToSelector est bien définie dans le @protocol NSObject, mais il se trouve que ce protocol est adopté par la classe NSObject. Du coup tu n'as pas à  redéfinir l'implémentation de respondsToSelector dans chacune de tes classes. Et encore heureux !




    Super ! Merci Objective-C. Peux-tu imaginer que dans d'autres langages ce n'est pas le cas ?

    Dans ces langages, on n'a pas d'autres choix que de demander à  l'objet les méthodes qu'il supporte avant de les appeler.

    C'est pas idiot, ni dangereux. C'est comme ça.




    En fait le plus gênant dans ton approche de créer un @protocol avec autant de méthodes @required de type "hasMethodTiti" que tu as de méthodes @optional de type "titi", c'est qu'au lieu de garder l'intelligence du côté de la classe manipulant ton protocole, et donc de garder le principe fondamental d'encapsulation propre à  la POO, tu laisses tous ceux qui vont créer des classes implémentant ce protocole (...) la responsabilité de dire s'ils implémentent telle ou telle méthode... alors que tu peux déjà  le savoir toi-même par introspection grâce à  respondsToSelector justement.




    Pour moi hasTiti, c'est de l'encapsulation.

    Wikipedia : En POO, l'encapsulation est l'idée de protéger l'information contenue dans un objet et de ne proposer que des méthodes de manipulation de cet objet.

    ça colle non ?




    Je n'imagine même pas à  quoi ça pourrait ressembler pour des protocoles comme UITableViewDelegate, dont le nombre de méthodes optionnelles est assez conséquent... et pour lesquelles avec ta solution il faudrait autant de méthodes @required (à  lister dans le protocole, et surtout à  implémenter quand on code nos tableviews) pour indiquer si on implémente ou pas telle ou telle méthode ?


    En effet. Ah bah ça s'applique pas à  ce cas là  alors...

    Ca tombe bien je ne pensais pas à  ce cas là  (interne, 10000).




    Donc celui qui crée une classe se conformant à  ton protocole non seulement est obligé d'implémenter des méthodes et d'écrire des lignes de code inutiles (car dont tu pourrais te passer, en utilisant plutôt respondsToSelector dans ton framework et en plus garder la responsabilité de la gestion de tout ça plutôt que laisser l'utilisateur risquer de faire n'importe quoi),


    Oui, je crois qu'on l'a déjà  vu au début ça.




    en plus au lieu de réduire le nombre d'appels Objective-C (ce qui est le but de ce sujet, au départ, en proposant en quelques sortes de mettre en cache dans un bitfield les résultats de respondsToSelector pour réduire les appels) tu ajoutes potentiellement des indirections,


    Le bitfield, c'est vraiment une bonne idée, j'espère que la UTableView est codée comme ça.

    Je cherchais à  étendre le sujet à  d'autres cas (interne, 10000).

    En effet, il ya une indirection dans mon cas ce qui le met à  égalité avec respondsToSelector.

    Ensuite je fais juste return YES ou return NO, donc ça doit être plus rapide que respondsToSelector.

    Ca sera tj plus long que l'optim du bitfield qui est imparable mais statique et non scalable.




    et enfin c'est la meilleure manière de risquer des plantages. En effet, si l'utilisateur retourne YES à  hasMethodTiti alors qu'il n'implémente pas la méthode titi, ton code va planter.


    Oui ça va faire trois fois qu'on le voit ça. image/crazy.gif' class='bbc_emoticon' alt=' B) ' />

    Garbage In, Garbage Out... ça sûr ma bonne dame...
  • Je crois que j'ai atteint le nombre max de quote.


    Et même si l'utilisateur crée une classe A qui implémente ton protocole, mais n'implémente pas la méthode titi, et implémente hasMethodTiti en lui faisant correctement retourner "NO", bah si quelqu'un ensuite crée une sous-classe B de la classe A et implémente la méthode titi, cela ne va pas marcher du coup.




    Ah ça... je n'y avais pas pensé. Il va falloir redéfinir la méthode hasTiti dans la classe dérivée du coup.

    C'est un risque en effet.

    En même temps on peut voir le côté positif : si toute une famille de classe implémentent à  coup sur titi et toto, le développeur peut intercaler entre le protocol et les classes, une classe mère qui va implémenter les hasXXXX.
  • Vous avez sacrément phosphoré sur ce sujet. Un p'tit verre pour la peine ? Paracetamol, Doliprane ou aspirine effervescente ?
  • Si tu es persuadé que ça n'a que, ou presque que du positif pour ton projet, alors pourquoi perdre du temps à  nous demander notre avis? Je doute qu'on puisse avoir la même vision sur le projet quand bien même tu nous présenterais le contexte exact.
Connectez-vous ou Inscrivez-vous pour répondre.