Je vois généralement cette question posée dans l'autre sens, par exemple: Chaque ivar doit-il être une propriété? (et j'aime la réponse de bbum à ce Q).
J'utilise des propriétés presque exclusivement dans mon code. De temps en temps, cependant, je travaille avec un entrepreneur qui développe depuis longtemps sur iOS et qui est un programmeur de jeux traditionnel. Il écrit du code qui ne déclare presque aucune propriété et s'appuie sur ivars. Je suppose qu'il le fait parce que 1.) il y est habitué puisque les propriétés n'existaient pas toujours avant Objective C 2.0 (oct '07) et 2.) pour le gain de performance minimal de ne pas passer par un getter / setter.
Bien qu'il écrit du code qui ne fuit pas, je préférerais toujours qu'il utilise des propriétés plutôt que des ivars. Nous en avons parlé et il ne voit plus ou moins aucune raison d'utiliser des propriétés puisque nous n'utilisions pas KVO et qu'il a l'habitude de s'occuper des problèmes de mémoire.
Ma question est plus ... Pourquoi voudriez-vous jamais utiliser une période ivar - expérimentée ou non. Y a-t-il vraiment une telle différence de performances que l'utilisation d'un ivar serait justifiée?
Aussi comme point de clarification, je remplace les setters et les getters selon les besoins et j'utilise l'ivar qui correspond à cette propriété à l'intérieur du getter / setter. Cependant, en dehors d'un getter / setter ou d'un init, j'utilise toujours la self.myProperty
syntaxe.
Modifier 1
J'apprécie toutes les bonnes réponses. Une chose que j'aimerais aborder et qui semble incorrecte est qu'avec un ivar, vous obtenez l'encapsulation là où avec une propriété vous ne le faites pas. Définissez simplement la propriété dans une continuation de classe. Cela cachera la propriété aux étrangers. Vous pouvez également déclarer la propriété en lecture seule dans l'interface et la redéfinir comme readwrite dans l'implémentation comme:
// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;
et avoir dans la suite de la classe:
// readwrite within this file
@property (nonatomic, copy) NSString * name;
Pour l'avoir complètement "privé", ne le déclarez que dans la suite de la classe.
Réponses:
Encapsulation
Si l'ivar est privé, les autres parties du programme ne peuvent pas y accéder aussi facilement. Avec une propriété déclarée, les personnes intelligentes peuvent accéder et muter assez facilement via les accesseurs.
Performance
Oui, cela peut faire une différence dans certains cas. Certains programmes ont des contraintes où ils ne peuvent pas utiliser de messagerie objc dans certaines parties du programme (pensez en temps réel). Dans d'autres cas, vous voudrez peut-être y accéder directement pour plus de vitesse. Dans d'autres cas, c'est parce que la messagerie objc agit comme un pare-feu d'optimisation. Enfin, il peut réduire vos opérations de comptage de références et minimiser l'utilisation maximale de la mémoire (si cela est fait correctement).
Types non triviaux
Exemple: si vous avez un type C ++, l'accès direct est parfois la meilleure approche. Le type peut ne pas être copiable ou ne pas être simple à copier.
Multithreading
Beaucoup de vos ivars sont codépendants. Vous devez garantir l'intégrité de vos données dans un contexte multithread. Ainsi, vous pouvez favoriser l'accès direct à plusieurs membres dans les sections critiques. Si vous vous en tenez aux accesseurs pour les données codépendantes, vos verrous doivent généralement être réentrants et vous finirez souvent par faire beaucoup plus d'acquisitions (beaucoup plus parfois).
Exactitude du programme
Puisque les sous-classes peuvent remplacer n'importe quelle méthode, vous pouvez éventuellement voir qu'il existe une différence sémantique entre l'écriture dans l'interface et la gestion appropriée de votre état. L'accès direct pour l'exactitude du programme est particulièrement courant dans les états partiellement construits - dans vos initialiseurs et dans
dealloc
, il est préférable d'utiliser l'accès direct. Vous pouvez également trouver ce courant dans les mises en œuvre d'un accesseur, un constructeur de commodité,copy
,mutableCopy
et mises en œuvre archivage / sérialisation.Il est également plus fréquent que l'on passe du tout a un état d'esprit d' accesseur de lecture-écriture public à un état d'esprit qui cache bien ses détails / données d'implémentation. Parfois, vous devez contourner correctement les effets secondaires qu'un remplacement d'une sous-classe peut introduire afin de faire la bonne chose.
Taille binaire
Déclarer tout readwrite par défaut entraîne généralement de nombreuses méthodes d'accès dont vous n'avez jamais besoin, lorsque vous considérez l'exécution de votre programme pendant un moment. Cela ajoutera donc de la graisse à votre programme et aux temps de chargement.
Minimise la complexité
Dans certains cas, il est tout simplement inutile d'ajouter + type + maintenir tout cet échafaudage supplémentaire pour une variable simple telle qu'un booléen privé qui est écrit dans une méthode et lu dans une autre.
Cela ne veut pas du tout dire que l'utilisation de propriétés ou d'accesseurs est mauvaise - chacune présente des avantages et des restrictions importants. Comme de nombreux langages OO et approches de conception, vous devez également favoriser les accesseurs avec une visibilité appropriée dans ObjC. Il y aura des moments où vous devrez dévier. Pour cette raison, je pense qu'il est souvent préférable de restreindre les accès directs à l'implémentation qui déclare l'ivar (par exemple le déclarer
@private
).re Edit 1:
La plupart d'entre nous ont mémorisé comment appeler dynamiquement un accesseur caché (tant que nous connaissons le nom…). Pendant ce temps, la plupart d'entre nous n'ont pas mémorisé comment accéder correctement aux ivars qui ne sont pas visibles (au-delà de KVC). La continuation de classe aide , mais elle introduit des vulnérabilités.
Cette solution de contournement est évidente:
Maintenant, essayez-le avec un ivar uniquement, et sans KVC.
la source
@private
, le compilateur devrait interdire l'accès aux membres en dehors des méthodes de classe et d'instance - n'est-ce pas ce que vous voyez?Pour moi, c'est généralement la performance. Accéder à un ivar d'un objet est aussi rapide que d'accéder à un membre struct en C en utilisant un pointeur vers la mémoire contenant un tel struct. En fait, les objets Objective-C sont essentiellement des structures C situées dans une mémoire allouée dynamiquement. C'est généralement aussi rapide que votre code peut obtenir, même le code d'assemblage optimisé à la main ne peut pas être plus rapide que cela.
L'accès à un ivar via un getter / paramètre implique un appel de méthode Objective-C, qui est beaucoup plus lent (au moins 3-4 fois) qu'un appel de fonction C "normal" et même un appel de fonction C normal serait déjà plusieurs fois plus lent que accéder à un membre de structure. Selon les attributs de votre propriété, l'implémentation setter / getter générée par le compilateur peut impliquer un autre appel de fonction C aux fonctions
objc_getProperty
/objc_setProperty
, car celles-ci devrontretain
/copy
/autorelease
les objets selon les besoins et effectuer en outre le verrouillage de rotation pour les propriétés atomiques si nécessaire. Cela peut facilement devenir très coûteux et je ne parle pas d'être 50% plus lent.Essayons ça:
Production:
C'est 4,28 fois plus lent et c'était un int primitif non atomique, à peu près le meilleur des cas ; la plupart des autres cas sont encore pires (essayez un
NSString *
propriété !). Donc, si vous pouvez vivre avec le fait que chaque accès ivar est 4 à 5 fois plus lent qu'il ne pourrait l'être, utiliser les propriétés est bien (au moins en ce qui concerne les performances), cependant, il existe de nombreuses situations dans lesquelles une telle baisse de performance est complètement inacceptable.Mise à jour 20/10/2015
Certaines personnes affirment que ce n'est pas un problème du monde réel, le code ci-dessus est purement synthétique et vous ne le remarquerez jamais dans une application réelle. Bon alors, essayons un échantillon du monde réel.
Le code ci-dessous définit les
Account
objets. Un compte a des propriétés qui décrivent le nom (NSString *
), le sexe (enum
) et l'âge (unsigned
) de son propriétaire, ainsi qu'un solde (int64_t
). Un objet de compte a uneinit
méthode et uncompare:
méthode. lecompare:
méthode est définie comme suit: Ordres féminins avant les hommes, ordre alphabétique des noms, ordres jeunes avant vieux, ordres d'équilibre de bas en haut.En fait, il existe deux classes de comptes,
AccountA
etAccountB
. Si vous regardez leur implémentation, vous remarquerez qu'elles sont presque entièrement identiques, à une exception près: lacompare:
méthode.AccountA
les objets accèdent à leurs propres propriétés par méthode (getter), tandis que lesAccountB
objets accèdent à leurs propres propriétés par ivar. C'est vraiment la seule différence! Ils accèdent tous les deux aux propriétés de l'autre objet à comparer par getter (y accéder par ivar ne serait pas sûr! Et si l'autre objet est une sous-classe et a remplacé le getter?). Notez également que l'accès à vos propres propriétés en tant qu'ivars ne rompt pas l'encapsulation (les ivars ne sont toujours pas publics).La configuration du test est vraiment simple: créez 1 Mio de comptes aléatoires, ajoutez-les à un tableau et triez ce tableau. C'est tout. Bien sûr, il existe deux tableaux, un pour les
AccountA
objets et un pour lesAccountB
objets et les deux tableaux sont remplis de comptes identiques (même source de données). Nous chronométrons le temps nécessaire pour trier les tableaux.Voici la sortie de plusieurs courses que j'ai faites hier:
Comme vous pouvez le voir, le tri du tableau d'
AccountB
objets est toujours plus rapide que le tri du tableau d'AccountA
objets.Quiconque prétend que des différences d'exécution allant jusqu'à 1,32 seconde ne font aucune différence devrait mieux ne jamais faire de programmation d'interface utilisateur. Si je veux changer l'ordre de tri d'une grande table, par exemple, des différences de temps comme celles-ci font une énorme différence pour l'utilisateur (la différence entre une interface utilisateur acceptable et une interface utilisateur lente).
Dans ce cas également, l'exemple de code est le seul vrai travail effectué ici, mais à quelle fréquence votre code est-il juste un petit engrenage d'une horloge compliquée? Et si chaque engrenage ralentit tout le processus comme celui-ci, qu'est-ce que cela signifie pour la vitesse de l'ensemble du mouvement d'horlogerie à la fin? Surtout si une étape de travail dépend de la sortie d'une autre, ce qui signifie que toutes les inefficacités se résumeront. La plupart des inefficacités ne sont pas un problème en soi, c'est leur somme qui devient un problème pour l'ensemble du processus. Et un tel problème n'est rien qu'un profileur montrera facilement, car un profileur consiste à trouver des points chauds critiques, mais aucune de ces inefficacités n'est en soi des points chauds. Le temps processeur n'est que moyennement réparti entre eux, mais chacun d'entre eux n'en a qu'une infime fraction, il semble que l'optimiser soit une perte de temps totale. Et c'est vrai,
Et même si vous ne pensez pas en termes de temps CPU, parce que vous pensez que gaspiller du temps CPU est tout à fait acceptable, après tout «c'est gratuit», qu'en est-il des coûts d'hébergement de serveur causés par la consommation d'énergie? Qu'en est-il de l'autonomie de la batterie des appareils mobiles? Si vous écrivez la même application mobile deux fois (par exemple un propre navigateur Web mobile), une fois une version où toutes les classes accèdent à leurs propres propriétés uniquement par des getters et une fois où toutes les classes y accèdent uniquement par ivars, utiliser le premier constamment épuisera définitivement la batterie beaucoup plus rapide que l'utilisation de la seconde, même si elles sont fonctionnelles équivalentes et pour l'utilisateur la seconde serait même probablement même un peu plus rapide.
Voici maintenant le code de votre
main.m
fichier (le code repose sur l'activation d'ARC et assurez-vous d'utiliser l'optimisation lors de la compilation pour voir l'effet complet):la source
unsigned int
qui n'est jamais conservé / libéré, que vous utilisiez ARC ou non. La conservation / libération elle-même est coûteuse, donc la différence sera moindre car la gestion de la conservation ajoute une surcharge statique qui existe toujours, en utilisant directement setter / getter ou ivar; cependant, vous économiserez toujours la surcharge d'un appel de méthode supplémentaire si vous accédez directement à l'ivar. Ce n'est pas grave dans la plupart des cas, à moins que vous ne le fassiez plusieurs milliers de fois par seconde. Apple dit d'utiliser des getters / setters par défaut, sauf si vous êtes dans une méthode init / dealloc ou si vous avez repéré un goulot d'étranglement.copy
ne fera PAS une copie de sa valeur à chaque fois que vous y accédez. Le getter decopy
propriété est comme le getter d'une propriétéstrong
/retain
. Son code est fondamentalementreturn [[self->value retain] autorelease];
. Seul le setter copie la valeur et cela ressemblera à peu près à ceci[self->value autorelease]; self->value = [newValue copy];
, alors qu'unstrong
/retain
setter ressemble à ceci:[self->value autorelease]; self->value = [newValue retain];
La raison la plus importante est le concept OOP de dissimulation d'informations : si vous exposez tout via des propriétés et que vous permettez ainsi aux objets externes de jeter un coup d'œil sur les composants internes d'un autre objet, vous utiliserez ces éléments internes et compliquerez ainsi la modification de l'implémentation.
Le gain de «performances minimales» peut rapidement se résumer et devenir un problème. Je sais par expérience; Je travaille sur une application qui pousse vraiment les iDevices à leurs limites et nous devons donc éviter les appels de méthode inutiles (bien sûr uniquement lorsque cela est raisonnablement possible). Pour aider à atteindre cet objectif, nous évitons également la syntaxe de point car elle rend difficile la visualisation du nombre d'appels de méthode à première vue: par exemple, combien d'appels de méthode l'expression
self.image.size.width
déclenche-t-elle? En revanche, vous pouvez immédiatement dire avec[[self image] size].width
.De plus, avec une dénomination ivar correcte, KVO est possible sans propriétés (IIRC, je ne suis pas un expert du KVO).
la source
Sémantique
@property
peut exprimer que les ivars ne peuvent pas:nonatomic
etcopy
.@property
ne le peuvent pas:@protected
: public sur sous-classes, privé à l'extérieur.@package
: public sur frameworks sur 64 bits, privé à l'extérieur. Identique@public
à 32 bits. Voir Contrôle d'accès aux variables de classe et d'instance 64 bits d' Apple .id __strong *_objs
.Performance
Petite histoire: les ivars sont plus rapides, mais cela n'a pas d'importance pour la plupart des utilisations.
nonatomic
Les propriétés n'utilisent pas de verrous, mais direct ivar est plus rapide car il ignore l'appel des accesseurs. Pour plus de détails, lisez l' e-mail suivant sur lists.apple.com.la source
Propriétés vs variables d'instance est un compromis, en fin de compte, le choix se résume à l'application.
Encapsulation / masquage des informations C'est une bonne chose (TM) du point de vue de la conception, des interfaces étroites et un lien minimal sont ce qui rend le logiciel maintenable et compréhensible. Il est assez difficile dans Obj-C de cacher quoi que ce soit, mais les variables d'instance déclarées dans l' implémentation sont aussi proches que possible.
Performances Alors que "l'optimisation prématurée" est une mauvaise chose (TM), écrire du code peu performant simplement parce que vous le pouvez est au moins aussi mauvais. Il est difficile d'argumenter contre un appel de méthode plus cher qu'une charge ou un stockage, et dans le code intensif en calcul, le coût s'additionne rapidement.
Dans un langage statique avec des propriétés, comme C #, les appels aux setters / getters peuvent souvent être optimisés par le compilateur. Cependant, Obj-C est dynamique et la suppression de tels appels est beaucoup plus difficile.
Abstraction Un argument contre les variables d'instance dans Obj-C a toujours été la gestion de la mémoire. Avec les variables d'instance MRC, les appels à retenir / release / autorelease doivent être répartis dans tout le code, les propriétés (synthétisées ou non) gardent le code MRC en un seul endroit - le principe de l'abstraction qui est une bonne chose (TM). Cependant, avec GC ou ARC, cet argument disparaît, donc l'abstraction pour la gestion de la mémoire n'est plus un argument contre les variables d'instance.
la source
Les propriétés exposent vos variables à d'autres classes. Si vous avez juste besoin d'une variable uniquement relative à la classe que vous créez, utilisez une variable d'instance. Voici un petit exemple: les classes XML pour l'analyse RSS et autres parcourent un tas de méthodes déléguées et autres. Il est pratique d'avoir une instance de NSMutableString pour stocker le résultat de chaque passe différente de l'analyse. Il n'y a aucune raison pour qu'une classe extérieure ait besoin d'accéder ou de manipuler cette chaîne. Donc, vous le déclarez simplement dans l'en-tête ou en privé et y accédez dans toute la classe. Définir une propriété pour cela peut être utile uniquement pour s'assurer qu'il n'y a pas de problèmes de mémoire, en utilisant self.mutableString pour appeler les getter / setters.
la source
La rétrocompatibilité a été un facteur pour moi. Je ne pouvais utiliser aucune fonctionnalité d'Objective-C 2.0 car je développais des logiciels et des pilotes d'imprimante qui devaient fonctionner sur Mac OS X 10.3 dans le cadre d'une exigence. Je sais que votre question semblait ciblée sur iOS, mais je pensais que je partagerais toujours mes raisons de ne pas utiliser de propriétés.
la source