web-dev-qa-db-fra.com

Quelle est la meilleure façon de résoudre une collision d'espace de noms 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 les initiales, par ex. 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 ex. Adium préfixe les classes avec "AI" (car il n'y a pas d'entreprise derrière qui vous pourriez prendre 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 ajouter 2 à 4 lettres à un nom de classe en face est un espace de noms très, très limité. Par exemple. MS ou AI pourraient avoir des significations entièrement différentes (l'IA pourrait être l'intelligence artificielle par exemple) et certains autres développeurs pourraient décider de les utiliser et créer une classe de même nom. Bang , collision d'espace de noms.

D'accord, s'il s'agit d'une collision entre l'une de vos propres classes et celle d'un framework externe 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 modifier? Votre application établit un lien avec les 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 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 que certaines bibliothèques C ont 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 quand vous voulez utiliser le système open (), vous utilisez simplement open () et lorsque vous souhaitez utiliser l'autre, vous y accédez via l'identifiant myOpen.

Est-ce que quelque chose de similaire est possible dans 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?


Mise à 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 la 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 la source d'aucun des deux. La collision est déjà là et des conseils sur la façon dont elle aurait pu être évitée à l'avance n'aideront plus. Je peux les transmettre aux développeurs de ces frameworks et espérer qu'ils choisissent un meilleur espace de noms à l'avenir, mais pour l'instant je cherche une solution pour travailler avec les frameworks en ce moment dans une seule application. Des solutions pour rendre cela possible?

173
Mecki

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 version ultérieure, pas de prise en charge de 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 en utiliser une classe, 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 cadres, puis copier ou renommer les classes à l'intérieur de ce cadre, puis charger l'autre cadre. Il y a deux problèmes avec ceci. Tout d'abord, je n'ai pas pu trouver de fonction pour copier les données pointées pour renommer ou copier une classe, et toutes les autres classes de ce premier framework qui référencent la classe renommée référenceraient désormais la classe de l'autre framework.

Vous n'auriez pas besoin de copier ou 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 référençant la mauvaise classe.

EDIT: La différence fondamentale entre les runtimes 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 les symboles auxquels elles font référence, tandis que dans Objective-C, elles contiennent des représentations de chaîne du noms des symboles thsoe. 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'entre elles doit avoir son nom changé. Cependant, lorsque d'autres classes doivent accéder à l'une des classes portant ce nom, elles demanderont à la table de recherche son adresse, et la table de recherche ne renverra jamais l'adresse de la classe renommée en fonction du nom de la classe d'origine.

47
Michael Buckley

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 du compilateur Objective-C @compatibility_alias (Décrite ici ). Vous pouvez utiliser @compatibility_alias Pour "renommer" une classe, vous permettant de nommer votre classe en utilisant le 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 FQDN, puis créer un en-tête avec tous les @compatibility_alias (J'imagine que vous pourriez générer automatiquement cet en-tête).

L'inconvénient d'un préfixe 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 compilation, pas une fonction d'exécution donc NSClassFromString(ClassName) échouera (retourner nil) - vous devrez utiliser NSClassFromString(COM_WHATERVER_ClassName). Vous pouvez utiliser ibtool via la phase de génération pour modifier les noms de classe dans un nib/xib d'Interface Builder afin de ne pas avoir à écrire le COM_WHATEVER _... complet dans Interface Builder.

Dernière mise en garde: car il s'agit d'une directive de compilation (et d'une directive obscure), 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).

93
Barry Wark

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

  1. Dans tous les cas, informez les développeurs des deux cadres du conflit, et indiquez clairement que leur échec à l'éviter et/ou à le traiter vous cause de vrais problèmes commerciaux, qui pourraient se traduire par une perte de revenus commerciaux s'ils ne sont pas résolus. Insistez sur le fait que, tout en résolvant les conflits existants par classe est une solution moins intrusive, changer complètement leur préfixe (ou en utiliser un s'ils ne sont pas actuellement, et honte à eux!) Est le meilleur moyen de s'assurer qu'ils ne le feront pas. revoir le même problème.
  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 tel est le cas, vérifiez si le fournisseur fournira une version personnalisée de l'infrastructure qui n'inclut pas les classes en conflit. Sinon, soyez franc sur le fait que leur inflexibilité réduit votre retour sur investissement en utilisant leur cadre. Ne vous sentez pas mal d'être arrogant dans des limites raisonnables - le client a toujours raison. ;-)
  3. Si un framework est plus "dispensable", vous pouvez envisager de le remplacer par un autre framework (ou combinaison de code), tiers ou homebrew. (Ce dernier est le pire des cas, car il entraînera certainement des coûts commerciaux supplémentaires, à la fois pour le développement et la maintenance.) Si vous le faites, informez le fournisseur de ce cadre exactement pourquoi vous avez décidé de ne pas utiliser son cadre.
  4. Si les deux cadres sont jugés tout aussi indispensables à votre application, explorez les moyens de factoriser l'utilisation de l'un d'entre eux à un ou plusieurs processus distincts, en communiquant peut-être via DO comme l'a suggéré Louis Gerbarg. Selon le degré de communication, cela peut ne pas être aussi mauvais que vous pourriez vous y attendre. Plusieurs programmes (y compris QuickTime, je crois) utilisent cette approche pour fournir une sécurité plus granulaire fournie en utilisant profils sandbox de ceinture de sécurité 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 l'un de ces points. J'espère que vous serez en mesure de résoudre le conflit dès que possible. Bonne chance!

12
Quinn Taylor

C'est grossier, mais vous pouvez utiliser objets distribués afin de conserver une des classes uniquement dans une adresse de programmes subordonnés et RPC. Cela deviendra compliqué 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 runtimes modernes ou hérités, êtes-vous une architecture fat ou single, 32 ou 64 bits, quelles versions de système d'exploitation ciblez-vous, reliez-vous dynamiquement, reliez-vous statiquement, ou avez-vous le choix, et est-ce potentiellement correct de faire quelque chose qui pourrait nécessiter une maintenance pour les nouvelles mises à jour logicielles.

Si vous êtes vraiment désespéré, vous pouvez:

  1. Pas de lien direct avec l'une des bibliothèques
  2. Implémentez une version alternative des routines d'exécution objc qui modifie le nom au moment du chargement (consultez le projet objc4 , ce que vous devez faire exactement dépend d'un certain nombre de questions que j'ai posées ci-dessus, mais cela devrait être 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 corrigé et changera son nom de classe

Ce qui précède va être assez laborieux, et si vous avez besoin de l'implémenter sur plusieurs arches et différentes versions d'exécution, ce sera très désagréable, mais il peut certainement être fait fonctionner.

8
Louis Gerbarg

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 non en collision, puis en chargeant le cadre de la classe en collision? (cela nécessiterait que les frameworks en collision soient chargés à différents moments 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 aussi dynamiquement pour avoir la même disposition ivar, les mêmes méthodes méthodes/adresses d'implémentation, et ne différer que par leur nom (pour éviter la collision)

4
xtophyr

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 entrant en collision avec un nom alternatif - de la même longueur mais d'une orthographe différente (mais, recommandation, même longueur de nom)? Par nature désagréable.

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 (ni si cela fait une différence). Cependant, il y a au moins une chance extérieure que le changement de nom fonctionne. Cela pourrait aussi être une idée de minimiser la différence d'orthographe, de sorte que si les symboles sont dans un ordre trié dans un tableau, le changement de nom ne déplace pas les choses dans le désordre. Des choses comme la recherche binaire sont contrariées si le tableau qu'ils recherchent n'est pas trié comme prévu.

3
Jonathan Leffler

@compatibility_alias sera en mesure de résoudre les conflits d'espace de noms de classe, par exemple.

@compatibility_alias NewAliasClass OriginalClass;

Cependant, cela ne résoudra aucune des énumérations, des typedefs ou des collisions d'espace de noms de protocole . De plus, il ne fonctionne pas bien avec @class decls 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 pourrez probablement pas résoudre le problème d'espacement de noms avec seulement Compatibility_alias.

J'ai regardé n problème similaire au vôtre , mais j'avais accès à la source et je construisais les cadres. La meilleure solution que j'ai trouvée pour cela était d'utiliser @compatibility_alias conditionnellement avec #defines pour supporter les énumérations/typedefs/protocoles/etc. Vous pouvez le faire conditionnellement sur l'unité de compilation pour l'en-tête en question afin de minimiser le risque de développer des éléments dans l'autre framework en collision.

1
Michael Chinen

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 que #incluez uniquement les en-têtes de chaque bibliothèque dans l'implémentation des classes de wrapper, cela séparerait efficacement les collisions de noms.

Je n'ai pas assez d'expérience avec ça en objectif-c (pour commencer), mais je crois que c'est ce que je ferais en C.

1
chrish

Juste une pensée .. non testée ou prouvée et qui pourrait être un moyen de marque, mais avez-vous envisagé d'écrire un adaptateur pour les classes que vous utilisez à partir du plus simple des frameworks .. ou du moins de leurs interfaces?

Si vous deviez écrire un wrapper autour du plus simple des frameworks (ou celui qui est le moins accessible par les interfaces), 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 seuls les en-têtes its doivent être distribués, vous cacheriez efficacement le cadre sous-jacent et seriez libre de le combiner avec le deuxième cadre avec des conflits.

J'apprécie bien sûr qu'il y aura probablement des moments où vous devrez utiliser les classes des deux frameworks en même temps, cependant, vous pouvez fournir des usines pour d'autres adaptateurs de classe de ce framework. À l'arrière de ce point, je suppose que vous auriez besoin d'un peu de refactorisation pour extraire les interfaces que vous utilisez des deux cadres, ce qui devrait vous fournir un bon point de départ pour construire votre wrapper.

Vous pouvez vous appuyer 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 recompiler lorsque vous la modifiez.

Encore une fois, ce n'est en aucun cas prouvé, mais j'ai envie d'ajouter une perspective. J'espère que ça aide :)

0
mark

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 cadres de votre application. Une collision suggère que les deux font des choses similaires, et vous pourriez probablement contourner l'utilisation d'un cadre 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.

0
Allyn

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 communautaire pour éviter les collisions d'espace de noms. N'hésitez pas à ajouter le vôtre à cette liste, je crois que c'est à cela qu'il sert.

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

0
Ryan Townshend

Si la collision se produit uniquement au niveau du lien statique, vous pouvez choisir la bibliothèque à utiliser pour résoudre les symboles:

cc foo.o -ldog bar.o -lcat

Si foo.o Et bar.o Font référence au symbole rat, alors libdog résoudra foo.o Et rat et libcat résoudra bar.orat.

0
wcochran