web-dev-qa-db-fra.com

Pourquoi Objective-C ne prend-il pas en charge les méthodes privées?

J'ai vu un certain nombre de stratégies pour déclarer des méthodes semi-privées dans Objective-C, mais il ne semble pas y avoir de moyen de créer une méthode vraiment privée. Je reconnais que. Mais pourquoi en est-il ainsi? Chaque explication que j'ai essentiellement dit: "vous ne pouvez pas le faire, mais voici une approximation proche."

Il existe un certain nombre de mots clés appliqués à ivars (membres) qui contrôlent leur portée, par exemple @private, @public, @protected. Pourquoi cela ne peut-il pas être fait aussi pour les méthodes? Il semble que le runtime devrait être en mesure de prendre en charge. Y a-t-il une philosophie sous-jacente qui me manque? Est-ce délibéré?

122
Rob Jones

La réponse est ... eh bien ... simple. Simplicité et cohérence, en fait.

Objective-C est purement dynamique au moment de l'envoi de la méthode. En particulier, chaque envoi de méthode passe par le même point de résolution de méthode dynamique que tous les autres envois de méthode. Au moment de l'exécution, chaque implémentation de méthode a exactement la même exposition et toutes les API fournies par le runtime Objective-C qui fonctionnent avec les méthodes et les sélecteurs fonctionnent de la même manière sur toutes les méthodes.

Comme beaucoup l'ont répondu (ici et dans d'autres questions), les méthodes privées de compilation sont prises en charge; si une classe ne déclare pas une méthode dans son interface accessible au public, alors cette méthode pourrait tout aussi bien ne pas exister en ce qui concerne votre code. En d'autres termes, vous pouvez obtenir toutes les différentes combinaisons de visibilité souhaitées au moment de la compilation en organisant votre projet de manière appropriée.

Il y a peu d'avantages à dupliquer la même fonctionnalité dans le runtime. Cela ajouterait énormément de complexité et de frais généraux. Et même avec toute cette complexité, cela n'empêchera pas tous, sauf le développeur le plus occasionnel, d'exécuter vos méthodes soi-disant "privées".

EDIT: L'une des hypothèses que j'ai remarquées est que les messages privés devraient passer par le runtime, ce qui entraînerait une surcharge potentiellement importante. Est-ce absolument vrai?

Oui, ça l'est. Il n'y a aucune raison de supposer que l'implémenteur d'une classe ne voudrait pas utiliser l'ensemble des fonctionnalités Objective-C dans l'implémentation, et cela signifie qu'une répartition dynamique doit se produire. Cependant, il n'y a pas de raison particulière pour laquelle les méthodes privées ne peuvent pas être distribuées par une variante spéciale de objc_msgSend(), car le compilateur sait qu'elles sont privées; c'est-à-dire que cela pourrait être réalisé en ajoutant une table de méthode privée uniquement à la structure Class.

Il n'y aurait aucun moyen pour une méthode privée de court-circuiter cette vérification ou de sauter le runtime?

Il ne pouvait pas ignorer le runtime, mais le runtime ne le ferait pas doit nécessairement vérifier les méthodes privées.

Cela dit, il n'y a aucune raison qu'un tiers ne puisse pas délibérément appeler objc_msgSendPrivate() sur un objet, en dehors de l'implémentation de cet objet, et certaines choses (KVO, par exemple) devraient le faire. En effet, ce serait juste une convention et un peu mieux en pratique que de préfixer les sélecteurs de méthodes privées ou de ne pas les mentionner dans l'en-tête de l'interface.

Le faire, cependant, minerait la nature dynamique pure de la langue. Chaque envoi de méthode ne passerait plus par un mécanisme d'envoi identique. Au lieu de cela, vous vous retrouveriez dans une situation où la plupart des méthodes se comportent dans un sens et une petite poignée sont juste différentes.

Cela s'étend au-delà de l'exécution car il existe de nombreux mécanismes dans Cocoa construits en plus du dynamisme cohérent d'Objective-C. Par exemple, le codage des valeurs clés et l'observation des valeurs clés devraient soit être très fortement modifiés pour prendre en charge les méthodes privées - très probablement en créant une échappatoire exploitable - soit les méthodes privées seraient incompatibles.

102
bbum

Le runtime pourrait le supporter mais le coût serait énorme. Chaque sélecteur envoyé devrait être vérifié s'il est privé ou public pour cette classe, ou chaque classe devrait gérer deux tables de répartition distinctes. Ce n'est pas la même chose pour les variables d'instance car ce niveau de protection se fait au moment de la compilation.

De plus, le runtime devrait vérifier que l'expéditeur d'un message privé est de la même classe que le destinataire. Vous pouvez également contourner les méthodes privées; si la classe a utilisé instanceMethodForSelector:, il pourrait donner le IMP retourné à n'importe quelle autre classe pour qu'ils invoquent directement la méthode privée.

Les méthodes privées n'ont pas pu contourner la distribution des messages. Considérez le scénario suivant:

  1. Une classe AllPublic a une méthode d'instance publique doSomething

  2. Une autre classe HasPrivate a une méthode d'instance privée également appelée doSomething

  3. Vous créez un tableau contenant un nombre quelconque d'instances de AllPublic et HasPrivate

  4. Vous avez la boucle suivante:

    for (id anObject in myArray)
        [anObject doSomething];
    

    Si vous avez exécuté cette boucle à partir de AllPublic, le runtime devrait vous empêcher d'envoyer doSomething sur les instances de HasPrivate, mais cette boucle serait utilisable si elle se trouvait à l'intérieur de HasPrivate classe.

18
dreamlax

Les réponses affichées jusqu'ici font un bon travail pour répondre à la question d'un point de vue philosophique, donc je vais poser une raison plus pragmatique: qu'est-ce que l'on gagnerait à changer la sémantique de la langue? C'est assez simple pour "cacher" efficacement les méthodes privées. À titre d'exemple, imaginez que vous avez une classe déclarée dans un fichier d'en-tête, comme ceci:

@interface MyObject : NSObject {}
- (void) doSomething;
@end

Si vous avez besoin de méthodes "privées", vous pouvez également les mettre dans le fichier d'implémentation:

@interface MyObject (Private)
- (void) doSomeHelperThing;
@end

@implementation MyObject

- (void) doSomething
{
    // Do some stuff
    [self doSomeHelperThing];
    // Do some other stuff;
}

- (void) doSomeHelperThing
{
    // Do some helper stuff
}

@end

Bien sûr, ce n'est pas tout à fait la même chose que les méthodes privées C++/Java, mais c'est effectivement assez proche, alors pourquoi modifier la sémantique du langage, ainsi que le compilateur, le runtime, etc., pour ajouter un fonctionnalité déjà émulée de manière acceptable? Comme indiqué dans d'autres réponses, la sémantique de transmission des messages - et leur dépendance à l'égard de la réflexion à l'exécution - rendraient la gestion des messages "privés" non triviale.

13
mipadi

La solution la plus simple consiste simplement à déclarer certaines fonctions C statiques dans vos classes Objective-C. Ceux-ci ont uniquement une portée de fichier selon les règles C pour le mot-clé statique et à cause de cela, ils ne peuvent être utilisés que par des méthodes de cette classe.

Pas de chichi du tout.

7
Cromulent

Oui, cela peut être fait sans affecter le temps d'exécution en utilisant une technique déjà utilisée par le (s) compilateur (s) pour gérer le C++: nom-mangling.

Cela n'a pas été fait parce qu'il n'a pas été établi que cela résoudrait une difficulté considérable dans l'espace des problèmes de codage que d'autres techniques (par exemple, le préfixe ou le soulignement) sont capables de contourner suffisamment. IOW, vous avez besoin de plus de douleur pour surmonter des habitudes enracinées.

Vous pouvez apporter des correctifs à clang ou gcc qui ajoutent des méthodes privées à la syntaxe et génèrent des noms modifiés qu'il seul a reconnus lors de la compilation (et rapidement oubliés). Ensuite, d'autres membres de la communauté Objective-C pourraient déterminer si cela en valait la peine ou non. Cela sera probablement plus rapide de cette façon que d'essayer de convaincre les développeurs.

6
Huperniketes

Essentiellement, cela a à voir avec la forme d'appel de méthode d'Objective-C. N'importe quel message peut être envoyé à n'importe quel objet et l'objet choisit comment répondre au message. Normalement, il répondra en exécutant la méthode nommée d'après le message, mais il pourrait également répondre de plusieurs autres manières. Cela ne rend pas les méthodes privées complètement impossibles - Ruby le fait avec un système de transmission de messages similaire - mais cela les rend quelque peu gênantes.

Même l'implémentation de méthodes privées par Ruby est un peu déroutante pour les gens à cause de l'étrangeté (vous pouvez envoyer à l'objet n'importe quel message que vous aimez, sauf ceux de cette liste!). Essentiellement, Ruby le fait fonctionner en interdisant aux méthodes privées d'être appelées avec un récepteur explicite. Dans Objective-C, cela nécessiterait encore plus de travail car Objective-C n'a pas cette option.

4
Chuck

C'est un problème avec l'environnement d'exécution d'Objective-C. Alors que C/C++ compile en code machine illisible, Objective-C conserve toujours certains attributs lisibles par l'homme comme les noms de méthode comme chaînes . Cela donne à Objective-C la possibilité d'exécuter des fonctions réfléchissantes .

EDIT: Être un langage réflexif sans méthodes privées strictes rend Objective-C plus "Pythonic" en ce que vous faites confiance à d'autres personnes qui utilisent votre code plutôt que de restreindre ce méthodes qu'ils peuvent appeler. L'utilisation de conventions de dénomination comme les doubles tirets bas est destinée à cacher votre code à un codeur client occasionnel, mais n'empêchera pas les codeurs de faire un travail plus sérieux.

1
pokstad

Il y a deux réponses selon l'interprétation de la question.

La première consiste à masquer l'implémentation de la méthode à l'interface. Ceci est utilisé, généralement avec une catégorie sans nom (par exemple @interface Foo()). Cela permet à l'objet d'envoyer ces messages mais pas d'autres - bien que l'on puisse toujours remplacer accidentellement (ou autrement).

La deuxième réponse, en supposant qu'il s'agit de performances et d'inline, est rendue possible mais en tant que fonction C locale à la place. Si vous vouliez une méthode ‘private foo (NSString *arg)’, Vous feriez void MyClass_foo(MyClass *self, NSString *arg) et l’appelleriez comme une fonction C comme MyClass_foo(self,arg). La syntaxe est différente, mais elle agit avec le genre sain de caractéristiques de performance des méthodes privées de C++.

Bien que cela réponde à la question, je dois souligner que la catégorie sans nom est de loin la façon la plus courante de procéder en Objective-C.

1
AlBlue

Objective-C ne prend pas en charge les méthodes privées car il n'en a pas besoin.

En C++, chaque méthode doit être visible dans la déclaration de la classe. Vous ne pouvez pas avoir de méthodes que quelqu'un incluant le fichier d'en-tête ne peut pas voir. Donc, si vous voulez que les méthodes que le code en dehors de votre implémentation ne devrait pas utiliser, vous n'avez pas le choix, le compilateur doit vous donner un outil pour que vous puissiez lui dire que la méthode ne doit pas être utilisée, c'est le mot-clé "privé".

Dans Objective-C, vous pouvez avoir des méthodes qui ne figurent pas dans le fichier d'en-tête. Vous atteignez donc très facilement le même objectif en n'ajoutant pas la méthode au fichier d'en-tête. Il n'y a pas besoin de méthodes privées. Objective-C présente également l'avantage que vous n'avez pas besoin de recompiler chaque utilisateur d'une classe car vous avez modifié les méthodes privées.

Par exemple, les variables que vous deviez déclarer dans le fichier d'en-tête (plus maintenant), @private, @public et @protected sont disponibles.

0
gnasher729

Une réponse manquante est: parce que les méthodes privées sont une mauvaise idée du point de vue de l’évolutivité. Cela peut sembler une bonne idée de rendre une méthode privée lors de son écriture, mais c'est une forme de liaison précoce. Le contexte peut changer et un utilisateur ultérieur peut souhaiter utiliser une implémentation différente. Un peu provocateur: "Les développeurs agiles n'utilisent pas de méthodes privées"

D'une certaine manière, tout comme Smalltalk, Objective-C est destiné aux programmeurs adultes. Nous apprécions de savoir ce que le développeur d'origine a supposé que l'interface devrait être, et prenons la responsabilité de gérer les conséquences si nous devons changer d'implémentation. Alors oui, c'est de la philosophie, pas de la mise en œuvre.

0
Stephan Eggermont