Jaki jest najlepszy sposób rozwiązania kolizji przestrzeni nazw Objective-C?

174

Objective-C nie ma przestrzeni nazw; jest podobny do C, wszystko znajduje się w jednej globalnej przestrzeni nazw. Powszechną praktyką jest poprzedzanie klas inicjałami, np. Jeśli pracujesz w IBM, możesz poprzedzić je „IBM”; jeśli pracujesz dla firmy Microsoft, możesz użyć „MS”; i tak dalej. Czasami inicjały odnoszą się do projektu, np. Adium poprzedzone jest klasami „AI” (bo nie ma za tym firmy, która mogłaby wziąć inicjały). Apple poprzedza klasy z NS i mówi, że ten prefiks jest zarezerwowany tylko dla Apple.

Jak dotąd tak dobrze. Jednak dołączanie 2 do 4 liter do nazwy klasy z przodu to bardzo, bardzo ograniczona przestrzeń nazw. Na przykład MS lub AI mogą mieć zupełnie inne znaczenia (AI może być na przykład sztuczną inteligencją), a inny programista może zdecydować się na ich użycie i stworzyć równie nazwaną klasę. Bang , kolizja przestrzeni nazw.

Okay, jeśli jest to kolizja między jedną z twoich własnych klas a jedną z używanych przez ciebie zewnętrznych frameworków, możesz łatwo zmienić nazewnictwo swojej klasy, nic wielkiego. Ale co, jeśli używasz dwóch zewnętrznych frameworków, obu, których nie masz źródła i których nie możesz zmienić? Twoja aplikacja łączy się z nimi oboma i pojawiają się konflikty nazw. Jak byś sobie z tym poradził? Jaki jest najlepszy sposób na obejście ich, aby nadal można było korzystać z obu klas?

W C możesz obejść te problemy, nie łącząc się bezpośrednio z biblioteką, zamiast tego ładujesz bibliotekę w czasie wykonywania, używając dlopen (), a następnie znajdź symbol, którego szukasz, używając dlsym () i przypisujesz go do symbolu globalnego (który możesz dowolnie nazwać), a następnie uzyskać do niego dostęp za pośrednictwem tego globalnego symbolu. Np. Jeśli masz konflikt, ponieważ jakaś biblioteka C ma funkcję o nazwie open (), możesz zdefiniować zmienną o nazwie myOpen i wskazać na funkcję open () biblioteki, a więc kiedy chcesz użyć systemowej open () , po prostu używasz open (), a kiedy chcesz użyć drugiej, uzyskujesz do niej dostęp za pośrednictwem identyfikatora myOpen.

Czy coś podobnego jest możliwe w Objective-C, a jeśli nie, to czy jest jakieś inne sprytne, trudne rozwiązanie, którego możesz użyć do rozwiązania konfliktów przestrzeni nazw? Jakieś pomysły?


Aktualizacja:

Aby to wyjaśnić: odpowiedzi, które sugerują, jak z wyprzedzeniem uniknąć kolizji przestrzeni nazw lub jak stworzyć lepszą przestrzeń nazw, są z pewnością mile widziane; jednak nie przyjmuję ich jako odpowiedzi, ponieważ nie rozwiązują one mojego problemu. Mam dwie biblioteki i ich nazwy klas kolidują. Nie mogę ich zmienić; Nie mam źródła żadnego z nich. Kolizja już tam jest, a wskazówki, jak można było go uniknąć, już nie pomogą. Mogę przekazać je twórcom tych frameworków i mam nadzieję, że wybiorą lepszą przestrzeń nazw w przyszłości, ale na razie szukam rozwiązania do pracy z tymi frameworkami w ramach jednej aplikacji. Jakieś rozwiązania, które to umożliwią?

Mecki
źródło
7
Masz dobre pytanie (co zrobić, jeśli potrzebujesz dwóch frameworków, które mają kolizję nazw), ale jest ono ukryte w tekście. Popraw to, aby było jaśniej, a unikniesz prostych odpowiedzi, takich jak ta, którą masz teraz.
benzado
4
To jest moje największe zastrzeżenie w związku z obecnym projektem języka Objective-C. Spójrz na odpowiedzi poniżej; te, które faktycznie rozwiązują to pytanie (wyładowanie NSBundle, użycie DO itp.) to ohydne sztuczki, które po prostu nie powinny być potrzebne do czegoś tak trywialnego, jak unikanie konfliktu przestrzeni nazw.
erikprice
@erikprice: Amen. Uczę się obj-c i trafiam właśnie na ten problem. Przyszedłem tutaj, szukając prostego rozwiązania… kiepski.
Dave Mateer,
1
Dla przypomnienia, technicznie zarówno C, jak i Objective-C zapewniają obsługę wielu przestrzeni nazw - nie jest to jednak dokładnie to, czego szuka OP. Zobacz objectivistc.tumblr.com/post/3340816080/…
Hmm, tego nie wiedziałem. Coś w rodzaju okropnej decyzji projektowej, nie?
Nico

Odpowiedzi:

47

Jeśli nie musisz używać klas z obu frameworków w tym samym czasie, a celujesz w platformy, które obsługują wyładowywanie NSBundle (OS X 10.4 lub nowszy, brak obsługi GNUStep), a wydajność naprawdę nie jest dla Ciebie problemem, uważam, że że możesz załadować jedną platformę za każdym razem, gdy musisz użyć jej klasy, a następnie zwolnić ją i załadować drugą, gdy potrzebujesz użyć drugiej platformy.

Moim początkowym pomysłem było użycie NSBundle do załadowania jednej z ram, a następnie skopiowania lub zmiany nazwy klas w tej strukturze, a następnie załadowania drugiej struktury. Są z tym dwa problemy. Po pierwsze, nie mogłem znaleźć funkcji do kopiowania wskazanych danych w celu zmiany nazwy lub skopiowania klasy, a wszelkie inne klasy w tym pierwszym frameworku, które odwołują się do klasy o zmienionej nazwie, będą teraz odwoływać się do klasy z innej struktury.

Nie musiałbyś kopiować ani zmieniać nazwy klasy, gdyby istniał sposób na skopiowanie danych wskazanych przez IMP. Możesz utworzyć nową klasę, a następnie skopiować wartości ivars, metody, właściwości i kategorie. Dużo więcej pracy, ale jest to możliwe. Jednak nadal miałbyś problem z innymi klasami w ramach odwołującymi się do niewłaściwej klasy.

EDYCJA: Podstawową różnicą między środowiskami wykonawczymi C i Objective-C jest, jak rozumiem, kiedy biblioteki są ładowane, funkcje w tych bibliotekach zawierają wskaźniki do wszelkich symboli, do których się odwołują, podczas gdy w Objective-C zawierają reprezentacje ciągów nazwy tych symboli. Tak więc w swoim przykładzie możesz użyć dlsym, aby pobrać adres symbolu w pamięci i dołączyć go do innego symbolu. Inny kod w bibliotece nadal działa, ponieważ nie zmieniasz adresu oryginalnego symbolu. Objective-C używa tabeli odnośników do mapowania nazw klas na adresy i jest to mapowanie 1-1, więc nie możesz mieć dwóch klas o tej samej nazwie. Zatem aby załadować obie klasy, jedna z nich musi mieć zmienioną nazwę. Jednak gdy inne klasy potrzebują dostępu do jednej z klas o tej nazwie,

Michael Buckley
źródło
5
Wydaje mi się, że wyładowywanie pakietów nie było obsługiwane do 10.5 lub później.
Quinn Taylor
93

Prefiksowanie klas unikalnym prefiksem jest zasadniczo jedyną opcją, ale istnieje kilka sposobów, aby uczynić to mniej uciążliwym i brzydkim. Istnieje długa dyskusja opcji tutaj . Moją ulubioną jest @compatibility_aliasdyrektywa kompilatora Objective-C (opisana tutaj ). Możesz użyć @compatibility_aliasdo „zmiany nazwy” klasy, umożliwiając nazwanie swojej klasy przy użyciu FQDN lub innego takiego przedrostka:

@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

W ramach pełnej strategii możesz poprzedzić wszystkie swoje klasy unikalnym prefiksem, takim jak FQDN, a następnie utworzyć nagłówek ze wszystkimi @compatibility_alias(wyobrażam sobie, że możesz automatycznie wygenerować wspomniany nagłówek).

Wadą takiego prefiksowania jest to, że musisz wprowadzić prawdziwą nazwę klasy (np. COM_WHATEVER_ClassNamePowyżej) we wszystkim, co wymaga nazwy klasy z łańcucha oprócz kompilatora. Warto zauważyć, że @compatibility_aliasjest to dyrektywa kompilatora, a nie funkcja uruchomieniowa, więc NSClassFromString(ClassName)zawiedzie (zwróci nil) - będziesz musiał użyć NSClassFromString(COM_WHATERVER_ClassName). Za ibtoolpomocą fazy budowania można modyfikować nazwy klas w pliku nib / xib programu Interface Builder, aby nie trzeba było pisać pełnego COM_WHATEVER _... w programie Interface Builder.

Ostatnie zastrzeżenie: ponieważ jest to dyrektywa kompilatora (i to niejasna), może nie być przenoszona między kompilatorami. W szczególności nie wiem, czy działa z frontendem Clang z projektu LLVM, chociaż powinien działać z LLVM-GCC (LLVM używający frontendu GCC).

Barry Wark
źródło
4
Głosuj za zgodą na alias_zgodności, nie wiedziałem o tym! Dzięki. Ale to nie rozwiązuje mojego problemu. Nie jestem w stanie zmienić żadnych prefiksów w obu frameworkach, których używam, mam je tylko w formie binarnej i kolidują. Co mam z tym zrobić?
Mecki,
W którym pliku (.h lub .m) powinna znajdować się linia @compatibility_alias?
Alex Basson
1
@Alex_Basson @compatibility_alias powinno prawdopodobnie znaleźć się w nagłówku, aby było widoczne wszędzie tam, gdzie widoczna jest deklaracja @interface.
Barry Wark
Zastanawiam się, czy możesz użyć @compatibility_alias z nazwami protokołów, definicjami typów lub cokolwiek innego w tej sprawie?
Ali
Dobre myśli. Czy można użyć swizzling metody, aby uniemożliwić NSClassFromString (ClassName) zwracanie nil dla klas z aliasami? Prawdopodobnie trudno byłoby znaleźć wszystkie metody, które przyjęłyby nazwę klasy.
Dickey Singh
12

Kilka osób już udostępniło podstępny i sprytny kod, który może pomóc w rozwiązaniu problemu. Niektóre sugestie mogą działać, ale wszystkie są mniej niż idealne, a niektóre z nich są wręcz nieprzyjemne do wdrożenia. (Czasami brzydkie hacki są nieuniknione, ale staram się ich unikać, kiedy tylko mogę.) Z praktycznego punktu widzenia, oto moje sugestie.

  1. W każdym razie poinformuj programistów o obu ramach konfliktu i wyjaśnij, że ich niepowodzenie w unikaniu konfliktu i / lub radzeniu sobie z nim powoduje rzeczywiste problemy biznesowe, które mogą przełożyć się na utratę dochodów biznesowych, jeśli nie zostaną rozwiązane. Podkreśl, że chociaż rozwiązywanie istniejących konfliktów dla poszczególnych klas jest mniej inwazyjną poprawką, to całkowita zmiana ich prefiksu (lub użycie takiego, jeśli nie są one obecnie, i wstyd!) Jest najlepszym sposobem, aby upewnić się, że tak się nie stanie. zobacz ponownie ten sam problem.
  2. Jeśli konflikty nazw są ograniczone do stosunkowo małego zestawu klas, sprawdź, czy możesz obejść tylko te klasy, zwłaszcza jeśli jedna z klas będących w konflikcie nie jest używana przez Twój kod, bezpośrednio lub pośrednio. Jeśli tak, sprawdź, czy dostawca dostarczy niestandardową wersję platformy, która nie zawiera klas będących w konflikcie. Jeśli nie, powiedz szczerze, że ich brak elastyczności zmniejsza zwrot z inwestycji w korzystanie z ich struktury. Nie czuj się źle, będąc nachalnym w granicach rozsądku - klient ma zawsze rację. ;-)
  3. Jeśli jeden framework jest bardziej „zbędny”, możesz rozważyć zastąpienie go innym frameworkiem (lub kombinacją kodu), czy to innej firmy, czy homebrew. (To ostatnie jest niepożądanym najgorszym przypadkiem, ponieważ z pewnością spowoduje dodatkowe koszty biznesowe, zarówno w zakresie rozwoju, jak i utrzymania). Jeśli to zrobisz, poinformuj sprzedawcę o tym frameworku dokładnie, dlaczego zdecydowałeś się nie używać jego frameworka.
  4. Jeśli oba frameworki są uważane za równie niezbędne dla twojej aplikacji, zbadaj sposoby rozłożenia użycia jednego z nich na jeden lub więcej oddzielnych procesów, być może komunikując się przez DO, jak zasugerował Louis Gerbarg. W zależności od stopnia komunikacji może to nie być tak złe, jak można by się spodziewać. Kilka programów (w tym, jak sądzę, QuickTime) stosuje to podejście w celu zapewnienia bardziej szczegółowego bezpieczeństwa zapewnianego przez profile piaskownicy pasów bezpieczeństwa w systemie Leopard , tak że tylko określony podzbiór kodu może wykonywać operacje krytyczne lub wrażliwe. Wydajność będzie kompromisem, ale może być Twoją jedyną opcją

Zgaduję, że opłaty licencyjne, warunki i czas trwania mogą uniemożliwić natychmiastową akcję w którymkolwiek z tych punktów. Miejmy nadzieję, że konflikt uda się jak najszybciej rozwiązać. Powodzenia!

Quinn Taylor
źródło
8

To jest obrzydliwe, ale możesz użyć rozproszonych obiektów , aby zachować jedną z klas tylko w podrzędnych programach adresowych i RPC do niej. Będzie to bałagan, jeśli będziesz przekazywać mnóstwo rzeczy w tę iz powrotem (i może nie być możliwe, jeśli obie klasy bezpośrednio manipulują widokami itp.).

Istnieją inne potencjalne rozwiązania, ale wiele z nich zależy od konkretnej sytuacji. W szczególności, czy korzystasz z nowoczesnych lub starszych środowisk wykonawczych, czy korzystasz z grubej lub pojedynczej architektury, 32 lub 64-bitowej, na jakie wydania systemu operacyjnego chcesz się kierować, czy łączysz dynamicznie, statycznie, czy masz wybór i czy jest to potencjalnie dobrze zrobić coś, co może wymagać konserwacji nowych aktualizacji oprogramowania.

Jeśli naprawdę jesteś zdesperowany, możesz:

  1. Nie łączy się bezpośrednio z żadną z bibliotek
  2. Zaimplementuj alternatywną wersję procedur wykonawczych objc, która zmienia nazwę w czasie ładowania ( sprawdź projekt objc4 , to, co dokładnie musisz zrobić, zależy od wielu pytań, które zadałem powyżej, ale powinno być możliwe bez względu na odpowiedzi ).
  3. Użyj czegoś takiego jak mach_override, aby wstrzyknąć nową implementację
  4. Załaduj nową bibliotekę zwykłymi metodami, przejdzie przez poprawioną procedurę linkera i zmieni nazwę klasy

Powyższe będzie dość pracochłonne, a jeśli będziesz musiał zaimplementować je w wielu archach i różnych wersjach środowiska uruchomieniowego, będzie to bardzo nieprzyjemne, ale z pewnością można to zmusić do pracy.

Louis Gerbarg
źródło
4

Czy rozważałeś użycie funkcji wykonawczych (/usr/include/objc/runtime.h) w celu sklonowania jednej z klas będących w konflikcie do klasy niekolidującej, a następnie załadowania struktury klas kolidujących? (wymagałoby to ładowania kolidujących struktur w różnych momentach, aby działały).

Możesz sprawdzać klasy ivars, metody (z nazwami i adresami implementacji) i nazwy w środowisku wykonawczym i tworzyć własne, a także dynamicznie, aby mieć ten sam układ ivar, nazwy metod / adresy implementacji i różnić się tylko nazwą (aby uniknąć kolizja)

xtophyr
źródło
3

Rozpaczliwe sytuacje wymagają desperackich środków. Czy rozważałeś zhakowanie kodu wynikowego (lub pliku biblioteki) jednej z bibliotek, zmianę kolidującego symbolu na alternatywną nazwę - o tej samej długości, ale z inną pisownią (ale zalecenie, taką samą długość nazwy)? Z natury paskudny.

Nie jest jasne, czy Twój kod bezpośrednio wywołuje dwie funkcje o tej samej nazwie, ale różnych implementacjach, czy też konflikt jest pośredni (nie jest też jasne, czy ma to jakiekolwiek znaczenie). Istnieje jednak przynajmniej zewnętrzna szansa, że ​​zmiana nazwy zadziała. Może to być również pomysł, aby zminimalizować różnicę w pisowni, tak aby jeśli symbole były posortowane w tabeli, zmiana nazwy nie spowodowała zmiany kolejności. Takie rzeczy, jak wyszukiwanie binarne, denerwują się, jeśli przeszukiwana tablica nie jest posortowana zgodnie z oczekiwaniami.

Jonathan Leffler
źródło
Zmiana biblioteki na dysku nie wchodzi w grę, ponieważ licencja na to nie pozwala. Zmiana symboli w pamięci byłaby możliwa, ale nie widzę sposobu, w jaki sposób można to zrobić (ładowanie biblioteki do pamięci, modyfikowanie jej, a następnie przekazywanie do dynamicznego konsolidatora ... nie widzę żadnej metody).
Mecki
3
OK - czas zdecydować, która z dwóch bibliotek jest mniej ważna i znaleźć następną. I wyjaśnij temu, za który płacisz, ale rezygnujesz z tego, że powodem, dla którego się zmieniasz, jest ta kolizja i może ona utrzymać Twoją firmę, naprawiając Twój problem.
Jonathan Leffler
2

@compatibility_alias będzie w stanie rozwiązywać konflikty przestrzeni nazw klas, np

@compatibility_alias NewAliasClass OriginalClass;

Jednak nie rozwiąże to żadnych kolizji wyliczeń, typów definicji ani przestrzeni nazw protokołów . Co więcej, nie @classwspółgra dobrze z przednimi declami oryginalnej klasy. Ponieważ większość frameworków będzie dostarczać te nieklasowe rzeczy, takie jak typedef, prawdopodobnie nie będziesz w stanie rozwiązać problemu z przestrzenią nazw za pomocą tylko atrybutu kompatybilności.

Przyjrzałem się problemowi podobnemu do twojego , ale miałem dostęp do źródła i budowałem frameworki. Najlepszym rozwiązaniem, jakie znalazłem, było @compatibility_aliaswarunkowe użycie #defines do obsługi wyliczeń / typedefs / protocols / etc. Możesz to zrobić warunkowo w jednostce kompilującej dla danego nagłówka, aby zminimalizować ryzyko rozszerzenia zawartości w innym kolidującym frameworku.

Michael Chinen
źródło
1

Wygląda na to, że problem polega na tym, że nie można odwoływać się do plików nagłówkowych z obu systemów w tej samej jednostce tłumaczeniowej (pliku źródłowym). Jeśli utworzysz opakowania z celem-c wokół bibliotek (dzięki czemu będą one bardziej użyteczne w procesie) i tylko # uwzględnisz nagłówki dla każdej biblioteki w implementacji klas opakowujących, skutecznie oddzieliłoby to kolizje nazw.

Nie mam wystarczającego doświadczenia z tym w celu-c (dopiero zaczynam), ale uważam, że to właśnie zrobiłbym w C.

chrish
źródło
1
Ale czy nadal nie napotkałbyś kolizji, gdybyś próbował uwzględnić oba nagłówki otoki w tym samym pliku (ponieważ każdy z nich musiałby zawierać same nagłówki odpowiedniego frameworka)?
Wilco
0

Prefiksowanie plików to najprostsze rozwiązanie, jakie znam. Cocoadev ma stronę przestrzeni nazw, która jest wysiłkiem społeczności mającym na celu uniknięcie kolizji przestrzeni nazw. Możesz dodać własne do tej listy, uważam, że do tego służy.

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

Ryan Townshend
źródło
W jaki sposób prefiksowanie plików pomaga, jeśli obiekty zdefiniowane w plikach powodują problem? Z pewnością mogę manipulować plikami h, ale wtedy linker w ogóle nie znajdzie obiektów, gdy podoba mi się we frameworku: - /
Mecki
Uważam, że byłaby to raczej najlepsza praktyka niż rozwiązanie pierwotnego problemu.
Ali
To wcale nie odpowiada na pytanie
Madbreaks
0

Jeśli masz kolizję, sugerowałbym, abyś dobrze się zastanowił nad tym, jak możesz refaktoryzować jeden z frameworków z aplikacji. Wystąpienie kolizji sugeruje, że obaj robią podobne rzeczy i prawdopodobnie można obejść się za pomocą dodatkowego środowiska, po prostu refaktoryzując aplikację. Nie tylko rozwiązałoby to problem z przestrzenią nazw, ale także uczyniłoby kod bardziej niezawodnym, łatwiejszym w utrzymaniu i wydajniejszym.

W porównaniu z rozwiązaniem bardziej technicznym, gdybym był na twoim miejscu, byłby to mój wybór.

Allyn
źródło
0

Jeśli kolizja występuje tylko na poziomie łącza statycznego, możesz wybrać, która biblioteka jest używana do rozwiązywania symboli:

cc foo.o -ldog bar.o -lcat

Jeśli foo.oi bar.ozarówno odniesienie symbol ratwtedy libdogrozwiąże foo.o„s rati libcatrozwiąże bar.o” s rat.

wcochran
źródło
0

Tylko myśl ... nie przetestowana ani sprawdzona i może być słuszna, ale czy rozważałeś napisanie adaptera dla klasy, której używasz, z prostszych frameworków ... lub przynajmniej ich interfejsów?

Gdybyś miał napisać opakowanie wokół prostszego frameworka (lub tego, który ma najmniejszy dostęp do interfejsów), nie byłoby możliwe skompilowanie tego opakowania do biblioteki. Biorąc pod uwagę, że biblioteka jest wstępnie skompilowana i tylko jej nagłówki muszą być dystrybuowane, skutecznie ukrywałbyś podstawowy framework i mógłbyś swobodnie połączyć go z drugim frameworkiem ze zderzeniem.

Oczywiście doceniam fakt, że mogą się zdarzyć sytuacje, w których będziesz musiał używać klas z obu frameworków w tym samym czasie, jednak możesz dostarczyć fabryki dla kolejnych adapterów klas tego frameworka. Z drugiej strony myślę, że potrzebowałbyś trochę refaktoryzacji, aby wyodrębnić interfejsy, których używasz z obu frameworków, co powinno stanowić dobry punkt wyjścia do zbudowania opakowania.

Możesz rozbudowywać bibliotekę tak jak Ty i kiedy potrzebujesz dodatkowej funkcjonalności z opakowanej biblioteki i po prostu ponownie skompilować, gdy się zmieni.

Ponownie, w żaden sposób nie udowodniono, ale chciałem dodać perspektywę. mam nadzieję, że to pomoże :)

znak
źródło
-1

Jeśli masz dwie struktury, które mają tę samą nazwę funkcji, możesz spróbować dynamicznie załadować struktury. To będzie nieeleganckie, ale możliwe. Jak to zrobić z klasami Objective-C, nie wiem. Zgaduję, że NSBundleklasa będzie miała metody, które załadują określoną klasę.

MaddTheSane
źródło