Quelle est la meilleure façon de résoudre une collision de l’espace de nom Objective-C?

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 consiste à 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 se réfèrent au projet, par exemple les classes de préfixes Adium avec “AI” (car aucune entreprise ne peut sous-estimer les initiales). Apple préfixe les classes avec NS et dit que ce préfixe est réservé à Apple uniquement.

Jusqu’ici tout va bien. Mais append 2 à 4 lettres à un nom de classe à l’avant est un espace de noms très très limité. Par exemple, MS ou AI peuvent avoir des significations totalement différentes (par exemple, l’intelligence artificielle peut être une intelligence artificielle) et un autre développeur peut décider de les utiliser et de créer une classe également nommée. Bang , collision d’espace de noms.

Bon, s’il s’agit d’une collision entre l’une de vos classes et celle d’une structure externe que vous utilisez, vous pouvez facilement changer la dénomination de votre classe, ce qui n’est pas grave. Mais que se passe-t-il si vous utilisez deux frameworks externes, les deux frameworks auxquels vous n’avez pas la source et que vous ne pouvez pas modifier? Votre application est liée aux deux et vous obtenez des conflits de noms. Comment feriez-vous pour résoudre ces problèmes? Quelle est la meilleure façon de les contourner de telle manière que vous puissiez toujours utiliser les deux classes?

En C, vous pouvez contourner ces problèmes en ne vous liant pas directement à la bibliothèque. Au lieu de cela, vous chargez la bibliothèque à l’exécution, en utilisant dlopen (), puis en recherchant le symbole que vous souhaitez utiliser dlsym () peut nommer comme vous voulez) et ensuite accéder à travers 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 sur la fonction open () de la bibliothèque, donc lorsque vous voulez utiliser le système open () , vous utilisez juste open () et quand vous voulez utiliser l’autre, vous y accédez via l’identifiant myOpen.

Est-ce que quelque chose de similaire est possible dans Objective-C et, dans la négative, existe-t-il une autre solution astucieuse 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’espaces de noms à l’avance ou comment créer un meilleur espace de noms sont 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 entrent en collision. 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 cela aurait pu être évité à l’avance n’aideront plus. 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 cherche une solution pour travailler avec les frameworks dès maintenant dans une seule application. Des solutions pour rendre cela possible?

Si vous n’avez pas besoin d’utiliser des classes des deux frameworks en même temps et que vous ciblez des plates-formes prenant en charge le déchargement NSBundle (OS X 10.4 ou ultérieur, pas de support GNUStep) et que les performances ne vous posent aucun problème, Vous pouvez charger un framework à chaque fois que vous en avez besoin, 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 dans 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 toute autre classe de cette première structure faisant référence à la classe renommée ferait désormais référence à la classe de l’autre infrastructure.

Vous n’avez 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 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 faisant référence à la mauvaise classe.

EDIT: La différence fondamentale entre les runtimes C et Objective-C est, si je comprends bien, que lorsque les bibliothèques sont chargées, les fonctions de ces bibliothèques contiennent des pointeurs vers les symboles auxquels elles font référence, alors qu’en Objective-C, elles contiennent des représentations de chaînes de caractères. 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 correspondance pour mapper les noms de classe aux adresses. Il s’agit d’un mappage 1-1, vous ne pouvez donc pas avoir deux classes du même nom. Ainsi, pour charger les deux classes, l’une d’elles doit changer de nom. Cependant, lorsque d’autres classes doivent accéder à l’une des classes portant ce nom, elles demanderont à la table de consultation son adresse et la table de recherche ne retournera jamais l’adresse de la classe renommée en fonction du nom de la classe d’origine.

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 laid. Il y a une longue discussion sur les options ici . Mon préféré est la directive de compilation Objective-C @compatibility_alias (décrite ici ). Vous pouvez utiliser @compatibility_alias pour “renommer” une classe, ce qui vous permet de nommer votre classe en utilisant un nom de domaine complet ou un préfixe de ce type:

 @interface COM_WHATEVER_ClassName : NSObject @end @compatibility_alias ClassName COM_WHATEVER_ClassName // now ClassName is an alias for COM_WHATEVER_ClassName @implementation ClassName //OK //blah @end ClassName *myClass; //OK 

Dans le cadre d’une stratégie complète, vous pouvez préfixer toutes vos classes avec un préfixe unique tel que le nom de domaine complet, puis créer un en-tête avec tous les @compatibility_alias (j’imagine que vous pouvez générer automatiquement cet en-tête).

L’inconvénient de préfixer comme ceci est que vous devez entrer le vrai nom de la classe (par exemple COM_WHATEVER_ClassName ci-dessus) dans tout ce qui nécessite le nom de la classe d’une chaîne en dehors du compilateur. Notamment, @compatibility_alias est une directive du compilateur, pas une fonction d’exécution, donc NSClassFromSsortingng(ClassName) échouera (retourne nil ) – vous devrez utiliser NSClassFromSsortingng(COM_WHATERVER_ClassName) . Vous pouvez utiliser ibtool via la phase de construction pour modifier les noms de classe dans une nib / xib d’Interface Builder afin de ne pas avoir à écrire le COM_WHATEVER _… complet dans Interface Builder.

Avertissement final: comme il s’agit d’une directive de compilation (et d’une directive obscure), il peut ne pas être portable entre les compilateurs. En particulier, je ne sais pas si cela fonctionne avec l’interface Clang du projet LLVM, même si cela devrait fonctionner avec LLVM-GCC (LLVM utilisant l’interface GCC).

Plusieurs personnes ont déjà partagé un code compliqué et astucieux qui pourrait aider à résoudre le problème. Certaines suggestions peuvent fonctionner, mais toutes ne sont pas idéales et certaines sont carrément mauvaises à mettre en œuvre. (Parfois, les hacks laids sont inévitables, mais j’essaie de les éviter chaque fois que je le peux.) D’un sharepoint vue pratique, voici mes suggestions.

  1. Dans tous les cas, informez les développeurs des deux frameworks du conflit et indiquez clairement que leur incapacité à les éviter et / ou à les gérer vous pose de véritables problèmes commerciaux, ce qui pourrait se traduire par une perte de revenus si ces derniers ne sont pas résolus. Insistez sur le fait que si la résolution des conflits existants par classe est une solution moins intrusive, changer complètement leur préfixe (ou en utiliser un si ce n’est pas le cas actuellement et leur en faire honte!) Est le meilleur moyen de s’assurer qu’ils ne le feront pas. voir le même problème à nouveau.
  2. Si les conflits de noms sont limités à un ensemble de classes raisonnablement petit, voyez si vous pouvez contourner uniquement ces classes, en particulier si l’une des classes en conflit n’est pas utilisée par votre code, directement ou indirectement. Si c’est le cas, voyez si le fournisseur fournira une version personnalisée de la structure qui n’inclut pas les classes en conflit. Sinon, soyez franc sur le fait que leur manque de flexibilité réduit votre retour sur investissement en utilisant leur framework. Ne vous sentez pas mal d’être insistant dans la mesure du raisonnable – le client a toujours raison. 😉
  3. Si un framework est plus “dispensable”, vous pouvez envisager de le remplacer par un autre framework (ou une combinaison de code), tiers ou homebrew. (Ce dernier est le pire des cas indésirables, car il entraînera certainement des coûts supplémentaires, tant pour le développement que pour la maintenance.) Si vous le faites, informez le fournisseur de la raison pour laquelle vous avez décidé de ne pas utiliser leur framework.
  4. Si les deux frameworks sont jugés également indispensables à votre application, explorez les moyens de prendre en compte l’utilisation de l’un d’entre eux dans un ou plusieurs processus distincts, en communiquant peut-être via DO, comme suggéré par Louis Gerbarg. Selon le degré de communication, cela peut ne pas être aussi grave que prévu. Plusieurs programmes (y compris QuickTime, je crois) utilisent cette approche pour fournir une sécurité plus granulaire en utilisant des profils de sandbox Seatbelt dans Leopard , de sorte que seul un sous-ensemble spécifique de votre code est autorisé à effectuer des opérations critiques ou sensibles. La performance sera un compromis, mais peut être votre seule option

Je suppose que les frais de licence, les conditions et les durées peuvent empêcher une action instantanée sur ces points. J’espère que vous serez en mesure de résoudre le conflit dès que possible. Bonne chance!

Ceci est grossier, mais vous pouvez utiliser des objects dissortingbués afin de ne garder que l’une des classes dans une adresse de programme subordonnée et RPC. Cela deviendra désordonné si vous passez une tonne de choses dans les deux sens (et cela peut ne pas être possible si les deux classes manipulent directement les vues, etc.).

Il existe d’autres solutions potentielles, mais beaucoup d’entre elles dépendent de la situation exacte. En particulier, utilisez-vous les environnements d’exécution modernes ou hérités, avez-vous une architecture simple ou 32 ou 64 bits, quelles cibles OS ciblez-vous, reliez-vous dynamicment, reliez-vous statiquement ou avez-vous le choix? 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 pourriez faire est:

  1. Pas de lien direct avec l’une des bibliothèques directement
  2. Implémentez une autre version des routines d’exécution objc qui changent le nom au moment du chargement ( consultez le projet objc4 , ce que vous devez faire dépend de certaines des questions que j’ai posées ci-dessus, mais cela devrait être possible quelles que soient les réponses) ).
  3. Utilisez quelque chose comme mach_override pour injecter votre nouvelle implémentation
  4. Chargez la nouvelle bibliothèque en utilisant des méthodes normales, elle passera par la routine de l’éditeur de liens et obtiendra son className modifié

Ce qui précède va exiger beaucoup de travail, et si vous devez l’implémenter contre plusieurs archs et différentes versions d’exécution, cela sera très désagréable, mais cela peut certainement être fait pour fonctionner.

Avez-vous envisagé d’utiliser les fonctions d’exécution (/usr/include/objc/runtime.h) pour cloner une des classes en conflit dans une classe sans collision, puis charger le cadre de la classe en collision? (Cela nécessiterait que les frameworks de 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 vos propres aussi dynamicment pour avoir la même disposition ivar, les noms des méthodes / adresses d’implémentation et collision)

Des situations désespérées appellent des mesures désespérées. Avez-vous envisagé de pirater le code object (ou le fichier de bibliothèque) de l’une des bibliothèques, en changeant le symbole de mise en collision en un autre nom – de même longueur mais orthographe différente (mais, recommandation, même longueur de nom)? Insortingnsè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 clair non plus que cela fasse une différence). Cependant, il y a au moins une chance extérieure que le renommage fonctionne. Il serait peut-être judicieux également de minimiser la différence d’orthographe, de sorte que si les symboles sont dans un ordre sortingé dans un tableau, le changement de nom ne provoque pas de désordre. Des choses comme la recherche binary sont perturbées si le tableau qu’elles recherchent n’est pas dans l’ordre sortingé comme prévu.

Il semble que le problème soit que vous ne pouvez pas référencer les fichiers d’en-tête 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 (ce qui les rend plus utilisables dans le processus), et n’incluez que 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 Objective-C (je commence juste), mais je crois que c’est ce que je ferais en C.

@compatibility_alias sera capable de résoudre les conflits d’espaces de noms de classes, par exemple

 @compatibility_alias NewAliasClass OriginalClass; 

Cependant, cela ne résoudra aucun des énumérations d’énumération, typedefs ou d’espace de noms de protocole . De plus, il ne joue pas bien avec les versions avancées de @class de la classe originale. Étant donné que la plupart des frameworks viendront avec ces choses non-class comme typedefs, vous ne pourrez probablement pas résoudre le problème de namespacing avec simplement compatibilité_alias.

J’ai examiné un problème similaire au vôtre , mais j’avais access aux sources et construisais les frameworks. La meilleure solution que j’ai trouvée pour cela était d’utiliser @compatibility_alias conditionnellement avec #defines pour supporter les enums / typedefs / protocols / etc. Vous pouvez le faire conditionnellement sur l’unité de compilation pour l’en-tête en question afin de minimiser le risque d’extension de données dans l’autre infrastructure.

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 à append les vôtres à cette liste, je crois que c’est ce à quoi cela sert.

http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix

Si vous avez une collision, je vous suggère de réfléchir à la manière dont vous pourriez réorganiser l’un des frameworks de votre application. Avoir une collision suggère que les deux font des choses similaires et que vous pourriez probablement utiliser un framework supplémentaire simplement en modifiant votre application. Non seulement cela résoudrait votre problème d’espace de noms, mais cela rendrait votre code plus robuste, plus facile à gérer et plus efficace.

Sur une solution plus technique, si j’étais à votre place, ce serait mon choix.

Si la collision se situe uniquement au niveau du lien statique, vous pouvez choisir quelle bibliothèque est utilisée pour résoudre les symboles:

 cc foo.o -ldog bar.o -lcat 

Si foo.o et bar.o tous deux référence au symbole rat alors libdog résoudra le rat libcat et libcat résoudra le rat bar.o

Juste une pensée .. pas testé ou prouvé et pourrait être la voie à suivre mais avez-vous envisagé d’écrire un adaptateur pour la classe que vous utilisez du plus simple des frameworks .. ou du moins de leurs interfaces?

Si vous deviez écrire un wrapper autour des plus simples des frameworks (ou de ceux à qui vous accédez le moins), il ne serait pas possible de comstackr ce wrapper dans une bibliothèque. Étant donné que la bibliothèque est précompilée et que seuls ses en- têtes doivent être dissortingbués, vous masqueriez efficacement la structure sous-jacente et vous seriez libre de la combiner avec la deuxième infrastructure en conflit.

J’apprécie bien sûr qu’il y ait des moments où vous devez utiliser des classes des deux frameworks en même temps, mais vous pouvez fournir des fabriques pour d’autres adaptateurs de classes de cette infrastructure. Au verso de ce point, je suppose que vous aurez besoin d’un peu de refactorisation pour extraire les interfaces que vous utilisez des deux frameworks, ce qui devrait vous fournir un bon sharepoint départ pour créer votre wrapper.

Vous pouvez utiliser la bibliothèque comme vous et lorsque vous avez besoin de fonctionnalités supplémentaires de la bibliothèque encapsulée, et simplement recomstackr les modifications.

Encore une fois, rien n’a été prouvé, mais j’avais l’impression d’append une perspective. J’espère que cela aide 🙂

Si vous avez deux frameworks qui ont le même nom de fonction, vous pouvez essayer de charger dynamicment les frameworks. Ce sera inélégant, mais possible. Comment le faire avec les classes Objective-C, je ne sais pas. Je suppose que la classe NSBundle aura des méthodes qui chargeront une classe spécifique.