Objective-C n'a pas d'espaces de noms; c'est un peu comme C, tout est dans un espace de noms global. La pratique courante est de préfixer les classes avec des initiales, par exemple si vous travaillez chez IBM, vous pouvez les préfixer avec "IBM"; si vous travaillez pour Microsoft, vous pouvez utiliser "MS"; etc. Parfois, les initiales font référence au projet, par exemple Adium préfixe les classes avec "AI" (car il n'y a pas de société derrière cela, vous pouvez prendre les initiales). Apple préfixe les classes avec NS et indique que ce préfixe est réservé à Apple uniquement.
Jusqu'ici tout va bien. Mais ajouter 2 à 4 lettres à un nom de classe devant est un espace de noms très, très limité. Par exemple, MS ou AI pourrait avoir une signification entièrement différente (l'IA pourrait être une intelligence artificielle par exemple) et un autre développeur pourrait décider de les utiliser et de créer une classe également nommée. Bang , collision d'espace de noms.
D'accord, s'il s'agit d'une collision entre l'une de vos propres classes et l'un des frameworks externes que vous utilisez, vous pouvez facilement changer le nom de votre classe, ce n'est pas grave. Mais que faire si vous utilisez deux frameworks externes, les deux frameworks dont vous n'avez pas la source et que vous ne pouvez pas changer? Votre application est liée aux deux et vous obtenez des conflits de noms. Comment allez-vous résoudre ces problèmes? Quelle est la meilleure façon de les contourner de manière à pouvoir toujours utiliser les deux classes?
En C, vous pouvez contourner ces problèmes en ne liant pas directement à la bibliothèque, à la place vous chargez la bibliothèque au moment de l'exécution, en utilisant dlopen (), puis trouvez le symbole que vous recherchez en utilisant dlsym () et affectez-le à un symbole global (que vous pouvez nommer comme vous le souhaitez), puis accédez-y via ce symbole global. Par exemple, si vous avez un conflit parce qu'une bibliothèque C a une fonction nommée open (), vous pouvez définir une variable nommée myOpen et la faire pointer vers la fonction open () de la bibliothèque, donc lorsque vous souhaitez utiliser le système open () , il vous suffit d'utiliser open () et lorsque vous souhaitez utiliser l'autre, vous y accédez via l'identifiant myOpen.
Quelque chose de similaire est-il possible en Objective-C et sinon, existe-t-il une autre solution intelligente et délicate que vous pouvez utiliser pour résoudre les conflits d'espace de noms? Des idées?
Mettre à jour:
Juste pour clarifier ceci: les réponses qui suggèrent comment éviter les collisions d'espace de noms à l'avance ou comment créer un meilleur espace de noms sont certainement les bienvenues; cependant, je ne les accepterai pas comme réponse car ils ne résolvent pas mon problème. J'ai deux bibliothèques et leurs noms de classe se heurtent. Je ne peux pas les changer; Je n'ai pas la source de l'un ou l'autre. La collision est déjà là et des conseils sur la façon dont elle aurait pu être évitée à l'avance ne seront plus utiles. Je peux les transmettre aux développeurs de ces frameworks et espérer qu'ils choisiront un meilleur espace de noms à l'avenir, mais pour le moment, je recherche une solution pour travailler avec les frameworks dès maintenant dans une seule application. Des solutions pour rendre cela possible?
la source
Réponses:
Si vous n'avez pas besoin d'utiliser des classes des deux frameworks en même temps et que vous ciblez des plates-formes qui prennent en charge le déchargement NSBundle (OS X 10.4 ou version ultérieure, pas de support GNUStep), et que les performances ne sont vraiment pas un problème pour vous, je crois que vous pouvez charger un framework à chaque fois que vous devez utiliser une classe à partir de celui-ci, puis le décharger et charger l'autre lorsque vous devez utiliser l'autre framework.
Mon idée initiale était d'utiliser NSBundle pour charger l'un des frameworks, puis copier ou renommer les classes à l'intérieur de ce framework, puis charger l'autre framework. Il y a deux problèmes avec ceci. Premièrement, je n'ai pas trouvé de fonction pour copier les données pointées pour renommer ou copier une classe, et toutes les autres classes de ce premier cadre qui référencent la classe renommée feraient désormais référence à la classe de l'autre cadre.
Vous n'auriez pas besoin de copier ou de renommer une classe s'il y avait un moyen de copier les données pointées par un IMP. Vous pouvez créer une nouvelle classe, puis copier sur les ivars, méthodes, propriétés et catégories. Beaucoup plus de travail, mais c'est possible. Cependant, vous auriez toujours un problème avec les autres classes du framework référençant la mauvaise classe.
EDIT: La différence fondamentale entre les environnements d'exécution C et Objective-C est, si je comprends bien, lorsque les bibliothèques sont chargées, les fonctions de ces bibliothèques contiennent des pointeurs vers tous les symboles auxquels elles font référence, alors qu'en Objective-C, elles contiennent des représentations sous forme de chaîne du noms de ces symboles. Ainsi, dans votre exemple, vous pouvez utiliser dlsym pour obtenir l'adresse du symbole en mémoire et l'attacher à un autre symbole. L'autre code de la bibliothèque fonctionne toujours car vous ne modifiez pas l'adresse du symbole d'origine. Objective-C utilise une table de recherche pour mapper les noms de classe aux adresses, et c'est un mappage 1-1, vous ne pouvez donc pas avoir deux classes avec le même nom. Ainsi, pour charger les deux classes, l'une d'elles doit avoir son nom changé. Cependant, lorsque d'autres classes doivent accéder à l'une des classes portant ce nom,
la source
Préfixer vos classes avec un préfixe unique est fondamentalement la seule option, mais il existe plusieurs façons de rendre cela moins onéreux et moins laid. Il y a une longue discussion sur les options ici . Mon préféré est la
@compatibility_alias
directive du compilateur Objective-C (décrite ici ). Vous pouvez utiliser@compatibility_alias
pour "renommer" une classe, vous permettant de nommer votre classe en utilisant le FQDN ou un tel préfixe:Dans le cadre d'une stratégie complète, vous pouvez préfixer toutes vos classes avec un préfixe unique tel que le FQDN, puis créer un en-tête avec tous les
@compatibility_alias
(j'imagine que vous pouvez générer automatiquement ledit en-tête).L'inconvénient d'un préfixage comme celui-ci est que vous devez entrer le vrai nom de classe (par exemple
COM_WHATEVER_ClassName
ci-dessus) dans tout ce qui nécessite le nom de classe d'une chaîne en plus du compilateur. Notamment,@compatibility_alias
est une directive de compilateur, pas une fonction d'exécution, doncNSClassFromString(ClassName)
échouera (retournil
) - vous devrez utiliserNSClassFromString(COM_WHATERVER_ClassName)
. Vous pouvez utiliseribtool
via la phase de construction pour modifier les noms de classe dans un nib / xib Interface Builder afin de ne pas avoir à écrire le COM_WHATEVER _... complet dans Interface Builder.Dernière mise en garde: comme il s'agit d'une directive de compilateur (et obscure en plus), elle peut ne pas être portable entre les compilateurs. En particulier, je ne sais pas si cela fonctionne avec le frontend Clang du projet LLVM, bien qu'il devrait fonctionner avec LLVM-GCC (LLVM utilisant le frontend GCC).
la source
Plusieurs personnes ont déjà partagé un code délicat et intelligent qui pourrait aider à résoudre le problème. Certaines des suggestions peuvent fonctionner, mais toutes sont loin d'être idéales et certaines d'entre elles sont carrément désagréables à mettre en œuvre. (Parfois, les hacks laids sont inévitables, mais j'essaie de les éviter chaque fois que je le peux.) D'un point de vue pratique, voici mes suggestions.
Je suppose que les frais de licence, les conditions et les durées peuvent empêcher une action instantanée sur l'un de ces points. J'espère que vous serez en mesure de résoudre le conflit le plus rapidement possible. Bonne chance!
la source
C'est grossier, mais vous pouvez utiliser des objets distribués afin de ne conserver l'une des classes que dans une adresse de programmes subordonnés et RPC. Cela deviendra compliqué si vous passez une tonne de choses dans les deux sens (et peut ne pas être possible si les deux classes manipulent directement les vues, etc.).
Il existe d'autres solutions potentielles, mais beaucoup dépendent de la situation exacte. En particulier, utilisez-vous les environnements d'exécution modernes ou hérités, êtes-vous une architecture lourde ou unique, 32 ou 64 bits, quelles versions de système d'exploitation ciblez-vous, créez-vous des liaisons dynamiques, des liaisons statiques, ou avez-vous le choix, et est-ce potentiellement ok pour faire quelque chose qui pourrait nécessiter une maintenance pour les nouvelles mises à jour logicielles.
Si vous êtes vraiment désespéré, ce que vous pouvez faire est:
Ce qui précède va demander beaucoup de travail, et si vous devez l'implémenter contre plusieurs archs et différentes versions d'exécution, ce sera très désagréable, mais cela peut certainement fonctionner.
la source
Avez-vous envisagé d'utiliser les fonctions d'exécution (/usr/include/objc/runtime.h) pour cloner l'une des classes en conflit dans une classe sans collision, puis charger le cadre de classe en collision? (cela nécessiterait que les frameworks en collision soient chargés à des moments différents pour fonctionner.)
Vous pouvez inspecter les classes ivars, les méthodes (avec les noms et les adresses d'implémentation) et les noms avec le runtime, et créer les vôtres de manière dynamique pour avoir la même présentation ivar, les mêmes noms de méthodes / adresses d'implémentation, et ne différer que par le nom (pour éviter les collision)
la source
Les situations désespérées appellent des mesures désespérées. Avez-vous envisagé de pirater le code objet (ou le fichier de bibliothèque) de l'une des bibliothèques, en changeant le symbole de collision en un autre nom - de même longueur mais une orthographe différente (mais, recommandation, la même longueur de nom)? Intrinsèquement méchant.
Il n'est pas clair si votre code appelle directement les deux fonctions avec le même nom mais des implémentations différentes ou si le conflit est indirect (il n'est pas non plus clair si cela fait une différence). Cependant, il y a au moins une chance extérieure que le changement de nom fonctionne. Cela pourrait également être une idée de minimiser la différence d'orthographe, de sorte que si les symboles sont dans un ordre trié dans une table, le changement de nom ne déplace pas les choses dans le désordre. Des choses comme la recherche binaire sont perturbées si le tableau qu'ils recherchent n'est pas dans l'ordre de tri comme prévu.
la source
@compatibility_alias
sera capable de résoudre les conflits d'espace de noms de classe, par exempleCependant, cela ne résoudra aucun des enums, typedefs ou conflits d'espace de noms de protocole . De plus, il ne joue pas bien avec
@class
les déclinaisons avant de la classe d'origine. Étant donné que la plupart des frameworks seront livrés avec ces éléments non-classe tels que les typedefs, vous ne seriez probablement pas en mesure de résoudre le problème d'espacement de noms avec uniquement Compatibility_alias.J'ai examiné un problème similaire au vôtre , mais j'avais accès aux sources et je construisais les frameworks. La meilleure solution que j'ai trouvée pour cela était d'utiliser
@compatibility_alias
conditionnellement avec #defines pour prendre en charge les enums / typedefs / protocoles / etc. Vous pouvez le faire de manière conditionnelle sur l'unité de compilation pour l'en-tête en question afin de minimiser le risque d'étendre des éléments dans l'autre cadre de collision.la source
Il semble que le problème soit que vous ne pouvez pas référencer les fichiers d'en-têtes des deux systèmes dans la même unité de traduction (fichier source). Si vous créez des wrappers objective-c autour des bibliothèques (les rendant plus utilisables dans le processus), et que #incluez uniquement les en-têtes de chaque bibliothèque dans l'implémentation des classes wrapper, cela séparerait efficacement les collisions de noms.
Je n'ai pas assez d'expérience avec cela dans objective-c (je ne fais que commencer), mais je pense que c'est ce que je ferais en C.
la source
Préfixer les fichiers est la solution la plus simple que je connaisse. Cocoadev a une page d'espace de noms qui est un effort de la communauté pour éviter les collisions d'espaces de noms. N'hésitez pas à ajouter le vôtre à cette liste, je pense que c'est à cela qu'il sert.
http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix
la source
Si vous avez une collision, je vous suggère de réfléchir sérieusement à la façon dont vous pourriez refactoriser l'un des frameworks hors de votre application. Une collision suggère que les deux font des choses similaires, et vous pourriez probablement vous déplacer en utilisant un framework supplémentaire simplement en refactorisant votre application. Non seulement cela résoudrait votre problème d'espace de noms, mais cela rendrait votre code plus robuste, plus facile à maintenir et plus efficace.
Sur une solution plus technique, si j'étais à votre place, ce serait mon choix.
la source
Si la collision se situe uniquement au niveau de la liaison statique, vous pouvez choisir la bibliothèque utilisée pour résoudre les symboles:
Si
foo.o
et lesbar.o
deux font référence au symbole,rat
alorslibdog
résoudrafoo.o
« srat
etlibcat
résoudrabar.o
» srat
.la source
Juste une pensée .. non testé ou prouvé et pourrait être un moyen de la marque mais avez-vous envisagé d'écrire un adaptateur pour la classe que vous utilisez à partir du plus simple des frameworks .. ou au moins leurs interfaces?
Si vous deviez écrire un wrapper autour du plus simple des frameworks (ou de celle à laquelle vous accédez le moins), il ne serait pas possible de compiler ce wrapper dans une bibliothèque. Étant donné que la bibliothèque est précompilée et que seul son têtes doivent être distribués, vous masqueriez efficacement le cadre sous-jacent et seriez libre de le combiner avec le deuxième cadre en cas de conflit.
J'apprécie bien sûr qu'il y aura probablement des moments où vous devez utiliser des classes des deux frameworks en même temps, cependant, vous pouvez fournir des usines pour d'autres adaptateurs de classe de ce framework. Sur le dos de ce point, je suppose que vous auriez besoin d'un peu de refactorisation pour extraire les interfaces que vous utilisez des deux frameworks, ce qui devrait vous fournir un bon point de départ pour créer votre wrapper.
Vous pouvez construire sur la bibliothèque au fur et à mesure que vous avez besoin de fonctionnalités supplémentaires de la bibliothèque encapsulée, et simplement la recompiler lorsque vous changez.
Encore une fois, rien de prouvé mais j'avais envie d'ajouter une perspective. J'espère que ça aide :)
la source
Si vous avez deux frameworks qui ont le même nom de fonction, vous pouvez essayer de charger dynamiquement les frameworks. Ce sera inélégant, mais possible. Comment le faire avec les classes Objective-C, je ne sais pas. J'imagine que la
NSBundle
classe aura des méthodes qui chargeront une classe spécifique.la source