Qual è il modo migliore per risolvere una collisione dello spazio dei nomi Objective-C?


174

Objective-C non ha spazi dei nomi; è molto simile a C, tutto è all'interno di uno spazio dei nomi globale. La pratica comune è quella di aggiungere un prefisso alle classi con le iniziali, ad es. Se si lavora in IBM, è possibile prefissarle con "IBM"; se lavori per Microsoft, puoi usare "MS"; e così via. A volte le iniziali si riferiscono al progetto, ad esempio prefissi Adium classi con "AI" (in quanto non vi è alcuna compagnia dietro la quale si possano prendere le iniziali). Il prefisso Apple classifica le classi con NS e dice che questo prefisso è riservato solo ad Apple.

Fin qui tutto bene. Ma aggiungere da 2 a 4 lettere a un nome di classe davanti è uno spazio dei nomi molto, molto limitato. Ad esempio, MS o AI potrebbero avere significati completamente diversi (l'intelligenza artificiale potrebbe essere ad esempio l'intelligenza artificiale) e alcuni altri sviluppatori potrebbero decidere di usarli e creare una classe ugualmente denominata. Bang , collisione dello spazio dei nomi.

Ok, se questa è una collisione tra una delle tue classi e quella di un framework esterno che stai usando, puoi facilmente cambiare la denominazione della tua classe, senza grossi problemi. Ma cosa succede se si utilizzano due framework esterni, entrambi in cui non si dispone della fonte e che non è possibile modificare? L'applicazione si collega a entrambi e si verificano conflitti di nomi. Come faresti a risolverli? Qual è il modo migliore per aggirarli in modo tale da poter ancora utilizzare entrambe le classi?

In C puoi aggirare questi non collegandoli direttamente alla libreria, invece carichi la libreria in fase di esecuzione, usando dlopen (), quindi trova il simbolo che stai cercando usando dlsym () e assegnalo a un simbolo globale (che può nominare come preferisci) e quindi accedervi tramite questo simbolo globale. Ad esempio, se si verifica un conflitto perché una libreria C ha una funzione denominata open (), è possibile definire una variabile denominata myOpen e farla puntare alla funzione open () della libreria, quindi quando si desidera utilizzare il sistema open () , usi semplicemente open () e quando vuoi usare l'altro, accedi ad esso tramite l'identificatore myOpen.

Qualcosa di simile è possibile in Objective-C e, in caso contrario, c'è qualche altra soluzione intelligente e complicata che puoi usare per risolvere i conflitti dello spazio dei nomi? Qualche idea?


Aggiornare:

Solo per chiarire questo: le risposte che suggeriscono come evitare in anticipo le collisioni dello spazio dei nomi o come creare uno spazio dei nomi migliore sono sicuramente benvenute; tuttavia, non li accetterò come risposta poiché non risolvono il mio problema. Ho due librerie e i loro nomi di classe si scontrano. Non posso cambiarli; Non ho la fonte di nessuno dei due. La collisione è già lì e i suggerimenti su come avrebbe potuto essere evitato in anticipo non aiuteranno più. Posso inoltrarli agli sviluppatori di questi framework e spero che in futuro scelgano uno spazio dei nomi migliore, ma per ora sto cercando una soluzione per lavorare con i framework in una sola applicazione. Qualche soluzione per renderlo possibile?


7
Hai una buona domanda (cosa fare se hai bisogno di due quadri che hanno una collisione di nomi) ma è sepolto nel testo. Rivedi per renderlo più chiaro ed eviterai risposte semplicistiche come quella che hai ora.
benzado,

4
Questa è la mia più grande lamentela con l'attuale design del linguaggio Objective-C. Guarda le risposte qui sotto; quelli che affrontano effettivamente la domanda (scaricamento di NSBundle, utilizzo di DO, ecc.) sono hack orribili che non dovrebbero essere necessari per qualcosa di così banale come evitare un conflitto nello spazio dei nomi.
erikprice

@erikprice: Amen. Sto imparando obj-c, e ho affrontato proprio questo problema. Sono venuto qui alla ricerca di una soluzione semplice ... zoppo.
Dave Mateer,

1
Per la cronaca, tecnicamente sia C che Objective-C forniscono supporto per più spazi dei nomi, ma non esattamente ciò che l'OP sta cercando. Vedi objectivistc.tumblr.com/post/3340816080/…

Hmm, non lo sapevo. Una specie di terribile decisione progettuale no?
Nico,

Risposte:


47

Se non hai bisogno di usare le classi di entrambi i framework contemporaneamente e stai prendendo di mira piattaforme che supportano lo scaricamento di NSBundle (OS X 10.4 o successivo, nessun supporto GNUStep) e le prestazioni non sono un problema per te, credo che è possibile caricare un framework ogni volta che è necessario utilizzare una classe da esso, quindi scaricarlo e caricare l'altro quando è necessario utilizzare l'altro framework.

La mia idea iniziale era di usare NSBundle per caricare uno dei framework, quindi copiare o rinominare le classi all'interno di quel framework e quindi caricare l'altro framework. Ci sono due problemi con questo. Innanzitutto, non sono riuscito a trovare una funzione per copiare i dati puntati a rinominare o copiare una classe e qualsiasi altra classe in quel primo framework che fa riferimento alla classe rinominata farebbe ora riferimento alla classe dall'altro framework.

Non sarebbe necessario copiare o rinominare una classe se esistesse un modo per copiare i dati indicati da un IMP. È possibile creare una nuova classe e quindi copiare su ivars, metodi, proprietà e categorie. Molto più lavoro, ma è possibile. Tuttavia, avresti ancora un problema con le altre classi nel framework che fanno riferimento alla classe sbagliata.

EDIT: La differenza fondamentale tra i tempi di esecuzione C e Objective-C è, a quanto ho capito, quando le librerie sono caricate, le funzioni in quelle librerie contengono puntatori a qualsiasi simbolo a cui fanno riferimento, mentre in Objective-C contengono rappresentazioni di stringhe del nomi dei simboli thsoe. Quindi, nel tuo esempio, puoi usare dlsym per ottenere l'indirizzo del simbolo in memoria e collegarlo a un altro simbolo. L'altro codice nella libreria funziona ancora perché non stai modificando l'indirizzo del simbolo originale. Objective-C utilizza una tabella di ricerca per mappare i nomi delle classi agli indirizzi, ed è una mappatura 1-1, quindi non puoi avere due classi con lo stesso nome. Pertanto, per caricare entrambe le classi, è necessario cambiare il nome di una di esse. Tuttavia, quando altre classi devono accedere a una delle classi con quel nome,


5
Credo che lo scarico dei bundle non sia stato supportato fino alla 10.5 o successive.
Quinn Taylor,

93

Il prefisso delle tue classi con un prefisso unico è fondamentalmente l'unica opzione, ma ci sono diversi modi per renderlo meno oneroso e brutto. C'è una lunga discussione sulle opzioni qui . La mia preferita è la @compatibility_aliasdirettiva del compilatore Objective-C (descritta qui ). Puoi usare @compatibility_aliasper "rinominare" una classe, permettendoti di nominare la tua classe usando FQDN o alcuni di questi prefissi:

@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

Come parte di una strategia completa, potresti aggiungere il prefisso a tutte le tue classi con un prefisso univoco come il nome di dominio completo (FQDN) e quindi creare un'intestazione con tutto il @compatibility_alias(immagino che potresti generare automaticamente tale intestazione).

Il rovescio della medaglia del prefisso come questo è che devi inserire il vero nome della classe (ad esempio COM_WHATEVER_ClassNamesopra) in tutto ciò che ha bisogno del nome della classe da una stringa oltre al compilatore. In particolare, @compatibility_aliasè una direttiva del compilatore, non una funzione di runtime, quindi NSClassFromString(ClassName)fallirà (ritorno nil) - dovrai usare NSClassFromString(COM_WHATERVER_ClassName). È possibile utilizzare ibtooltramite la fase di compilazione per modificare i nomi delle classi in un pennino / xib di Interface Builder in modo da non dover scrivere l'intero COM_WHATEVER _... in Interface Builder.

Avvertenza finale: poiché si tratta di una direttiva del compilatore (e alquanto oscura), potrebbe non essere portatile tra i compilatori. In particolare, non so se funziona con il frontend Clang del progetto LLVM, sebbene dovrebbe funzionare con LLVM-GCC (LLVM che utilizza il frontend GCC).


4
Voto per la compatibilità_alias, non lo sapevo! Grazie. Ma non risolve il mio problema. Non ho il controllo per modificare i prefissi di entrambi i framework che sto usando, li ho solo in forma binaria e si scontrano. Cosa dovrei fare al riguardo?
Mecki,

In quale file (.h o .m) dovrebbe andare la riga @compatibility_alias?
Alex Basson,

1
@Alex_Basson @compatibility_alias dovrebbe probabilmente andare nell'intestazione in modo che sia visibile ovunque sia visibile la dichiarazione @interface.
Barry Wark,

Mi chiedo se puoi usare @compatibility_alias con i nomi dei protocolli, le definizioni typedef o qualcos'altro?
Ali,

Buoni pensieri qui. È possibile utilizzare il metodo swizzling per impedire che NSClassFromString (ClassName) restituisca zero per le classi con alias? Probabilmente sarebbe difficile trovare tutti i metodi che hanno preso un nome di classe.
Dickey Singh,

12

Diverse persone hanno già condiviso alcuni codici complicati e intelligenti che potrebbero aiutare a risolvere il problema. Alcuni dei suggerimenti potrebbero funzionare, ma tutti sono tutt'altro che ideali, e alcuni sono decisamente cattivi da implementare. (A volte brutti hack sono inevitabili, ma cerco di evitarli ogni volta che posso.) Da un punto di vista pratico, ecco i miei suggerimenti.

  1. In ogni caso, informare gli sviluppatori di entrambi i framework del conflitto e chiarire che la loro incapacità di evitarlo e / o gestirlo sta causando problemi aziendali reali, che potrebbero tradursi in perdita di guadagni aziendali se non risolti. Sottolinea che, pur risolvendo i conflitti esistenti su una base per classe, è una soluzione meno invadente, cambiare del tutto il loro prefisso (o usarne uno se non lo sono attualmente e vergognarsene!) È il modo migliore per assicurarsi che non lo facciano vedere di nuovo lo stesso problema.
  2. Se i conflitti di denominazione sono limitati a un insieme di classi ragionevolmente piccolo, vedi se riesci a aggirare solo quelle classi, specialmente se una delle classi in conflitto non viene utilizzata dal tuo codice, direttamente o indirettamente. In tal caso, vedere se il fornitore fornirà una versione personalizzata del framework che non include le classi in conflitto. In caso contrario, sii sincero sul fatto che la loro inflessibilità sta riducendo il ROI dall'uso del loro framework. Non preoccuparti di essere invadente entro limiti ragionevoli: il cliente ha sempre ragione. ;-)
  3. Se un framework è più "dispensabile", potresti considerare di sostituirlo con un altro framework (o combinazione di codice), di terze parti o homebrew. (Quest'ultimo è il caso peggiore indesiderabile, poiché comporterà sicuramente costi aziendali aggiuntivi, sia per lo sviluppo che per la manutenzione.) In tal caso, informare il fornitore di quel framework esattamente perché si è deciso di non utilizzare il proprio framework.
  4. Se entrambi i framework sono considerati ugualmente indispensabili per la tua applicazione, esplora i modi per fattorizzare l'utilizzo di uno di essi a uno o più processi separati, magari comunicando tramite DO come suggerito da Louis Gerbarg. A seconda del grado di comunicazione, questo potrebbe non essere così male come ci si potrebbe aspettare. Diversi programmi (incluso QuickTime, credo) usano questo approccio per fornire una sicurezza più granulare fornita usando i profili sandbox Seatbelt in Leopard , in modo tale che solo un sottoinsieme specifico del codice è autorizzato a eseguire operazioni critiche o sensibili. Le prestazioni saranno un compromesso, ma potrebbero essere la tua unica opzione

Immagino che i costi, i termini e le durate delle licenze possano impedire un'azione immediata su uno di questi punti. Spero che sarai in grado di risolvere il conflitto il prima possibile. In bocca al lupo!


8

Questo è grossolano, ma è possibile utilizzare oggetti distribuiti per mantenere una delle classi solo in un indirizzo di programmi subordinati e RPC ad esso. Ciò diventerà disordinato se si passa un sacco di roba avanti e indietro (e potrebbe non essere possibile se entrambe le classi manipolano direttamente le visualizzazioni, ecc.).

Esistono altre soluzioni potenziali, ma molte dipendono dalla situazione esatta. In particolare, stai usando i runtime moderni o legacy, sei grasso o architettura singola, 32 o 64 bit, quali versioni del sistema operativo stai prendendo di mira, stai collegando dinamicamente, collegando staticamente o hai una scelta, ed è potenzialmente va bene fare qualcosa che potrebbe richiedere manutenzione per i nuovi aggiornamenti del software.

Se sei davvero disperato, quello che potresti fare è:

  1. Non collegare direttamente a una delle librerie
  2. Implementare una versione alternativa delle routine di runtime di objc che cambi il nome al momento del caricamento (controlla objc4 progetto , cosa esattamente devi fare dipende da una serie di domande che ho posto sopra, ma dovrebbe essere possibile indipendentemente dalle risposte ).
  3. Usa qualcosa come mach_override per iniettare la tua nuova implementazione
  4. Carica la nuova libreria usando i metodi normali, passerà attraverso la routine del linker patchato e cambierà il suo className

Quanto sopra sarà piuttosto laborioso, e se è necessario implementarlo su più archi e diverse versioni di runtime sarà molto spiacevole, ma può sicuramente essere fatto funzionare.


4

Hai mai pensato di utilizzare le funzioni di runtime (/usr/include/objc/runtime.h) per clonare una delle classi in conflitto su una classe non collidente e quindi caricare il framework delle classi in collisione? (ciò richiederebbe il caricamento dei framework di collisione in momenti diversi per funzionare).

È possibile ispezionare gli ivar delle classi, i metodi (con nomi e indirizzi di implementazione) e i nomi con il runtime e crearne uno proprio dinamicamente per avere lo stesso layout di ivar, nomi di metodi / indirizzi di implementazione e differire solo per nome (per evitare il collisione)


3

Situazioni disperate richiedono misure disperate. Hai mai considerato di hackerare il codice oggetto (o il file di libreria) di una delle librerie, cambiando il simbolo di collisione con un nome alternativo - della stessa lunghezza ma con una diversa ortografia (ma, raccomandazione, stessa lunghezza del nome)? Inerentemente cattivo.

Non è chiaro se il tuo codice chiama direttamente le due funzioni con lo stesso nome ma implementazioni diverse o se il conflitto è indiretto (né è chiaro se fa differenza). Tuttavia, esiste almeno una possibilità esterna che la ridenominazione funzioni. Potrebbe anche essere un'idea minimizzare la differenza nelle ortografie, in modo che se i simboli sono in un ordine in una tabella, la ridenominazione non sposta le cose fuori ordine. Cose come la ricerca binaria si sconvolgono se l'array che stanno cercando non è nell'ordine ordinato come previsto.


La modifica della libreria su disco è fuori discussione, poiché la licenza non lo consente. Cambiare i simboli in memoria sarebbe possibile, ma non vedo come ciò possa essere fatto (caricando la lib in memoria, modificandola e poi passandola al linker dinamico ... non vedo alcun metodo per quello).
Mecki,

3
OK: è il momento di decidere quale delle due librerie è meno importante e di trovare un sostituto. E spiega a quello per il quale paghi, ma che stai abbandonando il motivo per cui stai cambiando è a causa di questa collisione e possono mantenere la tua attività risolvendo il problema.
Jonathan Leffler,

2

@compatibility_alias sarà in grado di risolvere conflitti di spazio dei nomi di classe, ad es

@compatibility_alias NewAliasClass OriginalClass;

Tuttavia, ciò non risolverà nessuna delle collisioni enum, typedefs o dello spazio dei nomi di protocollo . Inoltre, non gioca bene con i @classdecl in avanti della classe originale. Dal momento che la maggior parte dei framework arriverà con queste cose non di classe come typedefs, probabilmente non saresti in grado di risolvere il problema dello spazio dei nomi solo con compatibilità_alias.

Ho osservato un problema simile al tuo , ma avevo accesso alla fonte e stavo costruendo i framework. La migliore soluzione che ho trovato per questo era usare @compatibility_aliascondizionatamente con #defines per supportare enums / typedefs / protocols / etc. È possibile farlo in modo condizionale sull'unità di compilazione per l'intestazione in questione per ridurre al minimo il rischio di espandere le cose nell'altro framework di collisioni.


1

Sembra che il problema sia che non è possibile fare riferimento ai file delle intestazioni di entrambi i sistemi nella stessa unità di traduzione (file di origine). Se si creano wrapper object-c attorno alle librerie (rendendole più utilizzabili nel processo) e solo #include le intestazioni per ciascuna libreria nell'implementazione delle classi wrapper, ciò separerebbe efficacemente le collisioni di nomi.

Non ho abbastanza esperienza con questo obiettivo-c (appena iniziato), ma credo che sia quello che farei in C.


1
Ma non ti imbatteresti comunque in collisioni se provassi a includere entrambe le intestazioni del wrapper nello stesso file (dal momento che ognuna dovrebbe includere le intestazioni dei rispettivi framework)?
Wilco,

0

Il prefisso dei file è la soluzione più semplice di cui sono a conoscenza. Cocoadev ha una pagina dello spazio dei nomi che è uno sforzo della comunità per evitare le collisioni dello spazio dei nomi. Sentiti libero di aggiungere il tuo a questo elenco, credo che sia quello che serve.

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


In che modo il prefisso dei file aiuta se gli oggetti definiti all'interno dei file causano il problema? Posso sicuramente manipolare i file h, ma poi il linker non troverà più gli oggetti quando mi piacerà contro il framework: - /
Mecki

Credo che questa sarebbe piuttosto una best practice piuttosto che una soluzione al problema iniziale.
Ali,

Questo non
risolve

0

Se hai una collisione, ti suggerirei di riflettere attentamente su come potresti refactoring uno dei framework fuori dalla tua applicazione. Avere una collisione suggerisce che i due stanno facendo cose simili così com'è, e probabilmente potresti aggirare utilizzando un framework aggiuntivo semplicemente riformattando la tua applicazione. Ciò non solo risolverebbe il problema del tuo spazio dei nomi, ma renderebbe il tuo codice più robusto, più facile da mantenere e più efficiente.

Oltre una soluzione più tecnica, se fossi nella tua posizione questa sarebbe la mia scelta.


0

Se la collisione è solo a livello di collegamento statico, puoi scegliere quale libreria utilizzare per risolvere i simboli:

cc foo.o -ldog bar.o -lcat

Se foo.oe bar.osia il simbolo di riferimento ratallora libdogrisolverà foo.o's rate libcatrisolverà bar.o' s rat.


0

Solo un pensiero .. non testato o provato e potrebbe essere il segno ma in fondo hai pensato di scrivere un adattatore per la classe che usi dal più semplice dei framework .. o almeno le loro interfacce?

Se dovessi scrivere un wrapper nel più semplice dei framework (o in quello a cui accedi di meno) non sarebbe possibile compilare quel wrapper in una libreria. Dato che la libreria è precompilata e solo le sue intestazioni devono essere distribuite, nasconderesti efficacemente il framework sottostante e saresti libero di combinarlo con il secondo framework con lo scontro.

Apprezzo ovviamente che ci siano probabilmente momenti in cui è necessario utilizzare le classi di entrambi i framework contemporaneamente, tuttavia è possibile fornire fabbriche per ulteriori adattatori di classe di quel framework. Sul retro di quel punto immagino che avresti bisogno di un po 'di refactoring per estrarre le interfacce che stai usando da entrambi i framework che dovrebbero fornire un buon punto di partenza per costruire il tuo wrapper.

Potresti basarti sulla libreria come te e quando hai bisogno di ulteriori funzionalità dalla libreria wrapped e semplicemente ricompilare quando cambi.

Ancora una volta, in nessun modo provato ma mi è sembrato di aggiungere una prospettiva. spero che sia d'aiuto :)


-1

Se si hanno due framework con lo stesso nome di funzione, è possibile provare a caricare dinamicamente i framework. Sarà inelegante, ma possibile. Come farlo con le classi Objective-C, non lo so. Immagino che la NSBundleclasse disporrà di metodi che caricheranno una classe specifica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.