it-swarm.com.de

Wie kann eine Objective-C-Namespace-Kollision am besten gelöst werden?

Objective-C hat keine Namespaces. Es ist ähnlich wie C, alles befindet sich in einem globalen Namespace. Es ist üblich, den Klassen Initialen voranzustellen, z. Wenn Sie bei IBM arbeiten, können Sie ihnen "IBM" voranstellen. Wenn Sie für Microsoft arbeiten, können Sie "MS" verwenden. und so weiter. Manchmal beziehen sich die Initialen auf das Projekt, z. Adium geht Klassen mit "KI" voran (da sich keine Firma dahinter befindet, die Sie mit den Initialen versehen könnten). Apple stellt Klassen mit dem Präfix NS voran und sagt, dass dieses Präfix nur für Apple reserviert ist.

So weit so gut. Das Anhängen von 2 bis 4 Buchstaben an einen Klassennamen ist jedoch ein sehr, sehr begrenzter Namespace. Z.B. MS oder KI können eine völlig andere Bedeutung haben (KI könnte beispielsweise künstliche Intelligenz sein), und einige andere Entwickler könnten sich dafür entscheiden, sie zu verwenden und eine gleichnamige Klasse zu erstellen. Bang , Namespace-Kollision.

Okay, wenn dies eine Kollision zwischen einer Ihrer eigenen Klassen und einem von Ihnen verwendeten externen Framework ist, können Sie die Benennung Ihrer Klasse leicht ändern, keine große Sache. Aber was ist, wenn Sie zwei externe Frameworks verwenden, beide Frameworks, zu denen Sie nicht die Quelle haben und die Sie nicht ändern können? Ihre Anwendung verknüpft sich mit beiden, und es treten Namenskonflikte auf. Wie würden Sie vorgehen, um diese Probleme zu lösen? Was ist der beste Weg, um sie so zu umgehen, dass Sie noch beide Klassen verwenden können?

In C können Sie diese umgehen, indem Sie keine direkte Verknüpfung mit der Bibliothek herstellen. Stattdessen laden Sie die Bibliothek zur Laufzeit mit dlopen (), suchen das gesuchte Symbol mit dlsym () und weisen es einem globalen Symbol zu (das Sie haben) kann beliebig benannt werden) und greift dann über dieses globale Symbol darauf zu. Z.B. Wenn Sie einen Konflikt haben, weil eine C-Bibliothek eine Funktion mit dem Namen open () hat, können Sie eine Variable mit dem Namen myOpen definieren und sie auf die Funktion open () der Bibliothek verweisen lassen. Wenn Sie also das System open () verwenden möchten, Sie verwenden einfach open () und wenn Sie den anderen verwenden möchten, greifen Sie über die myOpen-Kennung darauf zu.

Ist in Objective-C etwas Ähnliches möglich, und wenn nicht, gibt es eine andere clevere, knifflige Lösung, mit der Sie Namespace-Konflikte lösen können? Irgendwelche Ideen?


Aktualisieren:

Nur um dies zu verdeutlichen: Antworten, die vorschlagen, wie man Namespace-Kollisionen im Voraus vermeidet oder wie man einen besseren Namespace erstellt, sind auf jeden Fall willkommen. Ich werde sie jedoch nicht als die Antwort akzeptieren, da sie mein Problem nicht lösen. Ich habe zwei Bibliotheken und deren Klassennamen kollidieren. Ich kann sie nicht ändern. Ich habe keine der beiden Quellen. Die Kollision ist bereits da und Tipps, wie sie im Voraus hätte vermieden werden können, helfen nicht mehr. Ich kann sie an die Entwickler dieser Frameworks weiterleiten und hoffe, dass sie in Zukunft einen besseren Namespace auswählen. Derzeit suche ich jedoch nach einer Lösung, um mit den Frameworks in einer einzigen Anwendung zu arbeiten. Irgendwelche Lösungen, um dies zu ermöglichen?

173
Mecki

Wenn Sie nicht gleichzeitig Klassen aus beiden Frameworks verwenden müssen und auf Plattformen abzielen, die das Entladen von NSBundle unterstützen (OS X 10.4 oder höher, keine GNUStep-Unterstützung), ist die Leistung meines Erachtens für Sie kein Problem Sie können ein Framework jedes Mal laden, wenn Sie eine Klasse daraus verwenden müssen, und es dann entladen und das andere laden, wenn Sie das andere Framework verwenden müssen.

Meine ursprüngliche Idee war, mit NSBundle eines der Frameworks zu laden, dann die Klassen innerhalb dieses Frameworks zu kopieren oder umzubenennen und dann das andere Framework zu laden. Damit sind zwei Probleme verbunden. Erstens konnte ich keine Funktion zum Kopieren der Daten finden, auf die verwiesen wurde, um eine Klasse umzubenennen oder zu kopieren, und alle anderen Klassen in diesem ersten Framework, die auf die umbenannte Klasse verweisen, verweisen jetzt auf die Klasse aus dem anderen Framework.

Sie müssten keine Klasse kopieren oder umbenennen, wenn Sie die Daten kopieren könnten, auf die ein IMP verweist. Sie können eine neue Klasse erstellen und dann Ivars, Methoden, Eigenschaften und Kategorien kopieren. Viel mehr Arbeit, aber es ist möglich. Mit den anderen Klassen im Framework, die auf die falsche Klasse verweisen, treten jedoch weiterhin Probleme auf.

BEARBEITEN: Der grundlegende Unterschied zwischen den C- und Objective-C-Laufzeiten besteht meines Wissens darin, dass die Funktionen in diesen Bibliotheken beim Laden von Bibliotheken Zeiger auf Symbole enthalten, auf die sie verweisen, während sie in Objective-C Zeichenfolgendarstellungen der enthalten Namen der Symbole. In Ihrem Beispiel können Sie also dlsym verwenden, um die Adresse des Symbols im Speicher abzurufen und an ein anderes Symbol anzuhängen. Der andere Code in der Bibliothek funktioniert weiterhin, da Sie die Adresse des ursprünglichen Symbols nicht ändern. Objective-C verwendet eine Nachschlagetabelle, um Klassennamen Adressen zuzuordnen, und es handelt sich um eine 1: 1-Zuordnung, sodass Sie nicht zwei Klassen mit demselben Namen haben können. Um beide Klassen zu laden, muss der Name einer der Klassen geändert werden. Wenn jedoch andere Klassen auf eine der Klassen mit diesem Namen zugreifen müssen, fragen sie in der Nachschlagetabelle nach ihrer Adresse, und die Nachschlagetabelle gibt niemals die Adresse der umbenannten Klasse zurück, wenn der Name der ursprünglichen Klasse angegeben ist.

47
Michael Buckley

Das Präfixieren Ihrer Klassen mit einem eindeutigen Präfix ist grundsätzlich die einzige Option. Es gibt jedoch mehrere Möglichkeiten, dies weniger lästig und hässlich zu gestalten. Es gibt eine lange Diskussion über Optionen hier . Mein Favorit ist die Objective-C-Compiler-Direktive @compatibility_alias (Beschrieben hier ). Sie können @compatibility_alias Verwenden, um eine Klasse "umzubenennen", sodass Sie Ihre Klasse mit einem FQDN oder einem solchen Präfix benennen können:

@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

Als Teil einer vollständigen Strategie können Sie allen Klassen ein eindeutiges Präfix wie den vollqualifizierten Domänennamen voranstellen und dann einen Header mit dem Wert @compatibility_alias Erstellen (ich kann mir vorstellen, dass Sie diesen Header automatisch generieren).

Der Nachteil eines solchen Präfixes ist, dass Sie den wahren Klassennamen (z. B. COM_WHATEVER_ClassName Oben) in alles eingeben müssen, das den Klassennamen aus einer Zeichenfolge außer dem Compiler benötigt. Insbesondere ist @compatibility_alias Eine Compiler-Direktive und keine Laufzeitfunktion, sodass NSClassFromString(ClassName) fehlschlägt (nil zurückgeben) - Sie müssen NSClassFromString(COM_WHATERVER_ClassName) verwenden. Sie können ibtool über die Erstellungsphase verwenden, um Klassennamen in einer Interface Builder-NIB/XIB so zu ändern, dass Sie nicht das vollständige COM_WHATEVER _... in Interface Builder schreiben müssen.

Letzte Einschränkung: Da es sich um eine Compiler-Direktive handelt (und es sich dabei um eine undurchsichtige handelt), ist sie möglicherweise nicht zwischen Compilern übertragbar. Insbesondere weiß ich nicht, ob es mit dem Clang-Frontend aus dem LLVM-Projekt funktioniert, obwohl es mit LLVM-GCC (LLVM mit dem GCC-Frontend) funktionieren sollte.

93
Barry Wark

Einige Leute haben bereits einen kniffligen und cleveren Code geteilt, der zur Lösung des Problems beitragen könnte. Einige der Vorschläge mögen funktionieren, aber alle sind nicht ideal, und einige sind in der Umsetzung ausgesprochen unangenehm. (Manchmal sind hässliche Hacks unvermeidlich, aber ich versuche, sie zu vermeiden, wann immer ich kann.) Aus praktischer Sicht sind hier meine Vorschläge.

  1. Informieren Sie in jedem Fall die Entwickler über beide Frameworks des Konflikts und machen Sie deutlich, dass Sie das Problem dadurch vermeiden und/oder lösen können reale Geschäftsprobleme, die zu Einnahmeverlusten führen können, wenn sie nicht behoben werden. Betonen Sie, dass es weniger aufdringlich ist, bestehende Konflikte auf Klassenbasis zu lösen. Ändern Sie jedoch das Präfix vollständig (oder verwenden Sie eines, wenn dies nicht der Fall ist, und schämen Sie sich dafür!), Um sicherzustellen, dass dies nicht der Fall ist sehe das gleiche Problem noch einmal.
  2. Wenn die Namenskonflikte auf eine relativ kleine Menge von Klassen beschränkt sind, prüfen Sie, ob Sie nur diese Klassen umgehen können, insbesondere, wenn eine der in Konflikt stehenden Klassen weder direkt noch indirekt von Ihrem Code verwendet wird. Wenn dies der Fall ist, prüfen Sie, ob der Anbieter eine benutzerdefinierte Version des Frameworks bereitstellt, die die widersprüchlichen Klassen nicht enthält. Wenn nicht, seien Sie offen über die Tatsache, dass ihre Inflexibilität Ihren ROI davon abhält, ihr Framework zu nutzen. Fühlen Sie sich nicht schlecht, wenn Sie innerhalb der Vernunft aufdringlich sind - der Kunde hat immer Recht. ;-)
  3. Wenn ein Framework "überflüssiger" ist, können Sie es durch ein anderes Framework (oder eine Codekombination) ersetzen, das von einem Drittanbieter oder von Homebrew stammt. (Letzteres ist der unerwünschte schlimmste Fall, da hierdurch mit Sicherheit zusätzliche Geschäftskosten entstehen, sowohl für die Entwicklung als auch für die Wartung.) Wenn Sie dies tun, teilen Sie dem Anbieter dieses Frameworks genau mit, warum Sie sich entschieden haben, dessen Framework nicht zu verwenden.
  4. Wenn beide Frameworks für Ihre Anwendung gleichermaßen unverzichtbar sind, suchen Sie nach Möglichkeiten, die Verwendung eines dieser Frameworks für einen oder mehrere separate Prozesse zu berücksichtigen, und kommunizieren Sie möglicherweise über DO, wie von Louis Gerbarg vorgeschlagen. Je nach Kommunikationsgrad ist dies möglicherweise nicht so schlimm, wie Sie es erwarten. Einige Programme (einschließlich QuickTime, glaube ich) verwenden diesen Ansatz, um durch die Verwendung von Sicherheitsgurt-Sandbox-Profile in Leopard eine genauere Sicherheit zu gewährleisten, sodass nur eine bestimmte Teilmenge Ihres Codes kritische oder sensible Vorgänge ausführen kann . Leistung wird ein Kompromiss sein, kann aber Ihre einzige Option sein

Ich vermute, dass Lizenzgebühren, -bedingungen und -laufzeiten ein sofortiges Eingreifen in einem dieser Punkte verhindern können. Hoffentlich können Sie den Konflikt so schnell wie möglich lösen. Viel Glück!

12
Quinn Taylor

Das ist eklatant, aber Sie könnten verteilte Objekte verwenden, um eine der Klassen nur in einer untergeordneten Programmadresse und RPC zu belassen. Das wird chaotisch, wenn Sie eine Menge Dinge hin und her leiten (und ist möglicherweise nicht möglich, wenn beide Klassen Ansichten usw. direkt manipulieren).

Es gibt andere mögliche Lösungen, aber viele davon hängen von der genauen Situation ab. Verwenden Sie insbesondere die modernen oder älteren Laufzeiten, verwenden Sie eine fette oder einzelne 32- oder 64-Bit-Architektur, richten Sie sich auf Betriebssystemversionen, verknüpfen Sie dynamisch, statisch, oder haben Sie eine Wahl, und ist dies potenziell der Fall? OK, um etwas zu tun, das möglicherweise eine Wartung für neue Software-Updates erfordert.

Wenn Sie wirklich verzweifelt sind, können Sie Folgendes tun:

  1. Keine direkte Verknüpfung mit einer der Bibliotheken
  2. Implementieren Sie eine alternative Version der objc-Laufzeitroutinen, die den Namen zum Ladezeitpunkt ändert (checkout the objc4 project. Was genau Sie tun müssen, hängt von einer Reihe der oben gestellten Fragen ab, sollte es aber sein möglich, egal was die Antworten sind).
  3. Verwenden Sie etwas wie mach_override , um Ihre neue Implementierung einzufügen
  4. Laden Sie die neue Bibliothek mit normalen Methoden. Sie durchläuft die gepatchte Linker-Routine und lässt ihren Klassennamen ändern

Das Obige wird ziemlich arbeitsintensiv sein, und wenn Sie es gegen mehrere Bögen und verschiedene Laufzeitversionen implementieren müssen, wird es sehr unangenehm sein, aber es kann definitiv zum Funktionieren gebracht werden.

8
Louis Gerbarg

Haben Sie darüber nachgedacht, mithilfe der Laufzeitfunktionen (/usr/include/objc/runtime.h) eine der in Konflikt stehenden Klassen in eine nicht kollidierende Klasse zu klonen und anschließend das Framework für kollidierende Klassen zu laden? (Dies würde erfordern, dass die kollidierenden Frameworks zu unterschiedlichen Zeiten geladen werden, um zu funktionieren.)

Sie können die Klassen ivars, Methoden (mit Namen und Implementierungsadressen) und Namen mit der Laufzeit untersuchen und Ihre eigenen dynamisch erstellen, um dasselbe ivar-Layout, dieselben Methodennamen und Implementierungsadressen zu haben und sich nur nach Namen zu unterscheiden (um das zu vermeiden) Kollision)

4
xtophyr

Verzweifelte Situationen erfordern verzweifelte Maßnahmen. Haben Sie darüber nachgedacht, den Objektcode (oder die Bibliotheksdatei) einer der Bibliotheken zu hacken und das kollidierende Symbol in einen alternativen Namen zu ändern - mit der gleichen Länge, aber einer anderen Schreibweise (aber Empfehlung, der gleichen Länge des Namens)? Von Natur aus böse.

Es ist nicht klar, ob Ihr Code die beiden Funktionen mit dem gleichen Namen, aber unterschiedlichen Implementierungen direkt aufruft oder ob der Konflikt indirekt ist (noch ist klar, ob er einen Unterschied macht). Es besteht jedoch zumindest die Möglichkeit, dass das Umbenennen von außen funktioniert. Es könnte auch eine Idee sein, die Unterschiede in der Schreibweise so gering wie möglich zu halten, damit beim Umbenennen die Reihenfolge der Symbole in einer Tabelle nicht geändert wird. Dinge wie die binäre Suche werden ärgerlich, wenn das Array, das sie durchsuchen, nicht in der erwarteten Reihenfolge sortiert ist.

3

@compatibility_alias kann Klassennamensraumkonflikte lösen, z.

@compatibility_alias NewAliasClass OriginalClass;

Mit werden jedoch keine Enums, Typedefs oder Protokollnamespace-Kollisionen aufgelöst. Außerdem spielt es nicht gut mit @class forward decls der ursprünglichen Klasse. Da die meisten Frameworks mit solchen Nicht-Klassen-Dingen wie typedefs geliefert werden, können Sie das Namespace-Problem wahrscheinlich nicht nur mit compatible_alias beheben.

Ich habe ein ähnliches Problem wie deins angesehen, aber ich hatte Zugriff auf den Quellcode und baute die Frameworks auf. Die beste Lösung, die ich dafür gefunden habe, war die Verwendung von @compatibility_alias bedingt mit #defines zur Unterstützung der enums/typedefs/protocols/etc. Sie können dies auf der Kompiliereinheit für den betreffenden Header unter bestimmten Bedingungen ausführen, um das Risiko zu minimieren, dass in dem anderen kollidierenden Framework Inhalte erweitert werden.

1
Michael Chinen

Es scheint, dass das Problem darin besteht, dass Sie nicht auf Header-Dateien von beiden Systemen in derselben Übersetzungseinheit (Quelldatei) verweisen können. Wenn Sie Objective-C-Wrapper um die Bibliotheken erstellen (um sie in diesem Prozess benutzerfreundlicher zu machen) und nur die Header für jede Bibliothek in die Implementierung der Wrapper-Klassen einbeziehen, werden Namenskollisionen effektiv getrennt.

Ich habe nicht genug Erfahrung damit in Objective-C (fange gerade erst an), aber ich glaube, das würde ich in C tun.

1
chrish

Nur ein Gedanke .. nicht getestet oder bewährt und könnte wegweisend sein, aber haben Sie darüber nachgedacht, einen Adapter für die Klassen zu schreiben, die Sie aus dem einfacheren Framework verwenden .. oder zumindest deren Schnittstellen?

Wenn Sie einen Wrapper um das einfachere Framework (oder dasjenige, auf dessen Schnittstellen Sie am wenigsten zugreifen) schreiben würden, wäre es nicht möglich, diesen Wrapper in eine Bibliothek zu kompilieren. Wenn die Bibliothek vorkompiliert ist und nur seine Header verteilt werden müssen, können Sie das zugrunde liegende Framework effektiv ausblenden und es mit dem zweiten Framework kombinieren, indem Sie Konflikte verursachen.

Ich weiß natürlich zu schätzen, dass es wahrscheinlich Zeiten gibt, in denen Klassen aus beiden Frameworks gleichzeitig verwendet werden müssen. Sie könnten jedoch Fabriken für weitere Klassenadapter dieses Frameworks bereitstellen. Vor diesem Hintergrund müssten Sie wahrscheinlich ein wenig überarbeiten, um die von Ihnen verwendeten Schnittstellen aus beiden Frameworks zu extrahieren. Dies sollte einen guten Ausgangspunkt für die Erstellung Ihres Wrappers darstellen.

Sie können jederzeit auf die Bibliothek aufbauen, wenn Sie weitere Funktionen aus der umschlossenen Bibliothek benötigen, und sie einfach neu kompilieren, wenn Sie sie ändern.

Wieder in keiner Weise bewiesen, aber ich wollte eine Perspektive hinzufügen. ich hoffe es hilft :)

0
mark

Wenn Sie eine Kollision haben, sollten Sie sich überlegen, wie Sie eines der Frameworks in Ihrer Anwendung überarbeiten könnten. Eine Kollision deutet darauf hin, dass die beiden ähnliche Dinge tun, und Sie könnten wahrscheinlich mit einem zusätzlichen Framework umgehen, indem Sie Ihre Anwendung überarbeiten. Dies würde nicht nur Ihr Namespace-Problem lösen, sondern Ihren Code auch robuster, wartungsfreundlicher und effizienter machen.

Bei einer technischeren Lösung wäre dies meine Wahl, wenn ich in Ihrer Position wäre.

0
Allyn

Das Präfixieren der Dateien ist die einfachste mir bekannte Lösung. Cocoadev verfügt über eine Namespace-Seite, mit der sich die Community bemüht, Namespace-Kollisionen zu vermeiden. Fühlen Sie sich frei, Ihre eigene zu dieser Liste hinzuzufügen, ich glaube, das ist, wofür es ist.

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

0
Ryan Townshend

Wenn sich die Kollision nur auf der Ebene der statischen Verknüpfung befindet, können Sie auswählen, welche Bibliothek zum Auflösen von Symbolen verwendet wird:

cc foo.o -ldog bar.o -lcat

Ob foo.o und bar.o beide verweisen auf das Symbol rat dann wird libdog aufgelöst foo.o 's rat und libcat lösen bar.o 's rat.

0
wcochran