@class vs. #import

A mio avviso, è necessario utilizzare una dichiarazione forward-class nell’evento ClassA deve includere un’intestazione ClassB e ClassB deve includere un’intestazione ClassA per evitare inclusioni circolari. Capisco anche che un #import è un ifndef semplice in modo che una include solo una volta.

La mia richiesta è questa: quando si usa #import e quando si usa @class ? A volte se uso una dichiarazione @class , vedo un comune avvertimento del compilatore come il seguente:

warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.

Mi piacerebbe davvero comprenderlo, invece di rimuovere la @class forward-declaration e lanciare un #import in per silenziare gli avvertimenti che il compilatore mi sta dando.

Se vedi questo avviso:

avviso: il ricevitore “MyCoolClass” è una class diretta e l’interfaccia @ corrispondente potrebbe non esistere

devi @class il file, ma puoi farlo nel file di implementazione (.m) e utilizzare la dichiarazione @class nel file di intestazione.

@class non (di solito) rimuove la necessità di #import file, sposta semplicemente il requisito più vicino a dove l’informazione è utile.

Per esempio

Se dici @class MyCoolClass , il compilatore sa che può vedere qualcosa di simile:

 MyCoolClass *myObject; 

Non deve preoccuparsi di qualcosa di diverso da MyCoolClass è una class valida, e dovrebbe riservare spazio per un puntatore ad esso (in realtà, solo un puntatore). Pertanto, nella tua intestazione, @class sufficiente il 90% delle volte.

Tuttavia, se è necessario creare o accedere myObject membri di myObject , è necessario consentire al compilatore di sapere quali sono questi metodi. A questo punto (presumibilmente nel tuo file di implementazione), dovrai #import "MyCoolClass.h" , per dire al compilatore informazioni aggiuntive che vanno oltre “questa è una class”.

Tre semplici regole:

  • Solo #import la super class e i protocolli adottati nei file di intestazione (file .h ).
  • #import tutte le classi e i protocolli in cui si inviano messaggi all’implementazione (file .m ).
  • Dichiarazioni di inoltro per tutto il resto.

Se si inoltra la dichiarazione nei file di implementazione, probabilmente si sbaglia qualcosa.

Guarda la documentazione del linguaggio di programmazione Objective-C su ADC

Sotto la sezione sulla definizione di una class | Interfaccia di class descrive perché questo è fatto:

La direttiva @class riduce al minimo la quantità di codice vista dal compilatore e dal linker ed è quindi il modo più semplice per fornire una dichiarazione anticipata di un nome di class. Essendo semplice, evita potenziali problemi che potrebbero derivare dall’importazione di file che importano ancora altri file. Ad esempio, se una class dichiara una variabile di istanza tipizzata in modo statico di un’altra class e i loro due file di interfaccia si importano a vicenda, nessuna class può compilare correttamente.

Spero che aiuti.

Se necessario, utilizzare una dichiarazione #import nel file di intestazione e #import i file di intestazione per tutte le classi che si stanno utilizzando nell’implementazione. In altre parole, devi sempre #import i file che stai utilizzando nella tua implementazione, e se hai bisogno di fare riferimento a una class nel tuo file di intestazione usa anche una dichiarazione in avanti.

L’ eccezione è che si dovrebbe #import una class o un protocollo formale da cui si eredita il file di intestazione (nel qual caso non sarà necessario importarlo nell’implementazione).

La pratica comune è usare @class nei file di intestazione (ma è ancora necessario #importare la superclass) e #importare nei file di implementazione. Questo eviterà inclusioni circolari e funziona.

Un altro vantaggio: compilazione rapida

Se si include un file di intestazione, qualsiasi modifica al suo interno fa sì che anche il file corrente venga compilato, ma questo non è il caso se il nome della class è incluso come @class name . Ovviamente dovrai includere l’intestazione nel file sorgente

La mia richiesta è questa. Quando si usa #import e quando si usa @class?

Risposta semplice: #import o #include quando c’è una dipendenza fisica. Altrimenti, si usano dichiarazioni in avanti ( @class MONClass , struct MONStruct , @protocol MONProtocol ).

Ecco alcuni esempi comuni di dipendenza fisica:

  • Qualsiasi valore C o C ++ (un puntatore o riferimento non è una dipendenza fisica). Se hai un CGPoint come ivar o proprietà, il compilatore dovrà vedere la dichiarazione di CGPoint .
  • La tua superclass.
  • Un metodo che usi.

A volte se uso una dichiarazione @class, vedo un comune avvertimento del compilatore come il seguente: “warning: receiver ‘FooController’ è una class forward e l’interfaccia @ corrispondente potrebbe non esistere.”

Il compilatore è davvero molto indulgente in questo senso. Rilascerà suggerimenti (come quello sopra), ma puoi cestinare facilmente il tuo stack se li ignori e non #import correttamente. Anche se dovrebbe (IMO), il compilatore non lo impone. In ARC, il compilatore è più rigido perché è responsabile del conteggio dei riferimenti. Quello che succede è che il compilatore ricade su un valore predefinito quando incontra un metodo sconosciuto che chiami. Si presume che ogni valore di ritorno e parametro siano id . Quindi, dovresti eliminare ogni avvertimento dalle basi di codice perché questo dovrebbe essere considerato dipendenza fisica. Questo è analogo al chiamare una funzione C che non è dichiarata. Con C, si assume che i parametri siano int .

La ragione per cui si preferiscono le dichiarazioni anticipate è che è ansible ridurre i tempi di compilazione in base ai fattori, poiché esiste una dipendenza minima. Con le dichiarazioni in avanti, il compilatore vede che c’è un nome e può analizzare e compilare correttamente il programma senza vedere la dichiarazione della class o tutte le sue dipendenze quando non c’è dipendenza fisica. Le build pulite richiedono meno tempo. Le build incrementali richiedono meno tempo. Certo, finirai per passare un po ‘di tempo in più assicurandoti che tutte le intestazioni di cui hai bisogno siano visibili ad ogni traduzione di conseguenza, ma questo si ripaga rapidamente in tempi di costruzione ridotti (assumendo che il tuo progetto non sia piccolo).

Se invece usi #import o #include , stai facendo molto più lavoro al compilatore di quanto sia necessario. Stai anche introducendo dipendenze di intestazione complesse. Puoi paragonarlo a un algoritmo a forza bruta. Quando si #import , si trascinano tonnellate di informazioni non necessarie, che richiedono molta memoria, I / O del disco e CPU per analizzare e compilare le fonti.

ObjC è molto vicino all’ideale per un linguaggio basato su C per quanto riguarda la dipendenza perché i tipi NSObject non sono mai valori – I tipi NSObject sono sempre puntatori conteggiati di riferimento. In questo modo è ansible ottenere tempi di compilazione incredibilmente veloci se si strutturano le dipendenze del programma in modo appropriato e in avanti, laddove ansible, poiché è richiesta una minima dipendenza fisica. È inoltre ansible dichiarare le proprietà nelle estensioni di class per ridurre ulteriormente la dipendenza. Questo è un enorme vantaggio per i sistemi di grandi dimensioni: sapresti fare la differenza che fa se hai mai sviluppato una base di codice C ++ di grandi dimensioni.

Pertanto, la mia raccomandazione è quella di utilizzare in avanti, ove ansible, e quindi di #import dove c’è dipendenza fisica. Se vedi l’avviso o un altro che implica dipendenza fisica, correggili tutti. La soluzione è #import nel file di implementazione.

Durante la creazione di librerie, è probabile che classifichi alcune interfacce come un gruppo, nel qual caso devi #import quella libreria in cui viene introdotta la dipendenza fisica (ad esempio #import ). Questo può introdurre dipendenza, ma i manutentori della libreria possono spesso gestire le dipendenze fisiche per te, se necessario, se introducono una funzione, possono minimizzare l’impatto che ha sulle tue build.

Vedo un sacco di “Fallo così” ma non vedo alcuna risposta a “Perché?”

Quindi: perché dovresti @class nella tua intestazione e #import solo nella tua implementazione? Stai raddoppiando il tuo lavoro dovendo @class e #import tutto il tempo. A meno che tu non faccia uso dell’eredità. In tal caso, ti verrà # importato più volte per una singola @class. Quindi devi ricordarti di rimuovere da più file diversi se improvvisamente decidi che non hai più bisogno di accedere a una dichiarazione.

L’importazione dello stesso file più volte non è un problema a causa della natura di #import. Anche la performance di compilazione non è davvero un problema. Se lo fosse, non saremmo #importare Cocoa / Cocoa.h o simili in quasi tutti i file di intestazione che abbiamo.

se lo facciamo

 @interface Class_B : Class_A 

significhiamo che stiamo ereditando Class_A in Class_B, in Class_B possiamo accedere a tutte le variabili di class_A.

se lo stiamo facendo

 #import .... @class Class_A @interface Class_B 

qui diciamo che stiamo usando Class_A nel nostro programma, ma se vogliamo usare le variabili Class_A in Class_B dobbiamo #importare Class_A nel file .m (creare un object e usarne la funzione e le variabili).

per ulteriori informazioni sulle dipendenze dei file & #import & @class, controlla questo:

http://qualitycoding.org/file-dependencies/ it è un buon articolo

riassunto dell’articolo

importa nei file di intestazione:

  • #importa la superclass che stai ereditando e i protocolli che stai implementando.
  • Inoltra – dichiara tutto il resto (a meno che non provenga da un framework con un’intestazione principale).
  • Cerca di eliminare tutti gli altri #import.
  • Dichiarare i protocolli nelle proprie intestazioni per ridurre le dipendenze.
  • Troppe dichiarazioni in avanti? Hai una grande class.

importa nei file di implementazione:

  • Elimina le #importazioni non utilizzate.
  • Se un metodo delega a un altro object e restituisce ciò che viene restituito, prova a inoltrare-dichiarare quell’object invece di # importarlo.
  • Se includere un modulo ti obbliga ad includere un livello dopo l’altro di dipendenze successive, potresti avere un insieme di classi che vogliono diventare una libreria. Costruiscila come una libreria separata con un’intestazione principale, in modo che tutto possa essere portato come un singolo blocco predefinito.
  • Troppi #import? Hai una grande class.

Quando sviluppo, ho solo tre cose in mente che non mi causano mai problemi.

  1. Importa super classi
  2. Importa le classi genitore (quando hai figli e genitori)
  3. Importa classi al di fuori del tuo progetto (come in framework e librerie)

Per tutte le altre classi (sottoclassi e classi child nel mio progetto self), le dichiaro tramite forward-class.

Se provi a dichiarare una variabile, o una proprietà nel tuo file di intestazione, che non hai ancora importato, otterrai un errore che dice che il compilatore non conosce questa class.

Il tuo primo pensiero è probabilmente #import .
Ciò potrebbe causare problemi in alcuni casi.

Ad esempio, se implementi una serie di metodi C nel file di intestazione, nelle strutture o in qualcosa di simile, perché non dovrebbero essere importati più volte.

Pertanto puoi dire al compilatore con @class :

So che non conosci quella lezione, ma esiste. Sarà importato o implementato altrove

Fondamentalmente dice al compilatore di stare zitto e compilare, anche se non è sicuro se questa class verrà mai implementata.

Di solito utilizzi #import in .m e @class nei file .h .

Inoltra dichiarazione solo per impedire al compilatore di mostrare errori.

il compilatore saprà che c’è una class con il nome che hai usato nel tuo file di intestazione per dichiarare.

Il compilatore si lamenterà solo se userai quella class in modo tale che il compilatore debba conoscere la sua implementazione.

Ex:

  1. Questo potrebbe essere come se stai per derivare la tua class da esso o
  2. Se hai intenzione di avere un object di quella class come variabile membro (anche se raro).

Non si lamenterà se lo userai solo come puntatore. Ovviamente, è necessario # importarlo nel file di implementazione (se si crea un’istanza di un object di quella class) poiché è necessario conoscere il contenuto della class per creare un’istanza di un object.

NOTA: #import non è uguale a #include. Questo significa che non c’è nulla chiamato importazione circolare. import è una specie di richiesta per il compilatore di cercare in un particolare file per alcune informazioni. Se questa informazione è già disponibile, il compilatore la ignora.

Basta provare questo, importa Ah in Bh e Bh in Ah Non ci saranno problemi o lamentele e funzionerà anche bene.

Quando usare @class

Puoi usare @class solo se non vuoi nemmeno importare un’intestazione nella tua intestazione. Questo potrebbe essere un caso in cui non ti interessa nemmeno sapere quale sarà quella class. Casi in cui potresti non avere ancora un’intestazione per quella class.

Un esempio di ciò potrebbe essere che si stanno scrivendo due librerie. Una class, chiamiamola A, esiste in una libreria. Questa libreria include un’intestazione dalla seconda libreria. Quella intestazione potrebbe avere un puntatore di A, ma potrebbe non essere necessario usarla di nuovo. Se la libreria 1 non è ancora disponibile, la libreria B non verrà bloccata se si utilizza @class. Ma se stai cercando di importare Ah, allora il progresso della libreria 2 è bloccato.

Pensa a @class come al compilatore “fidati, questo esiste”.

Pensa a #import come copia-incolla.

Vuoi minimizzare il numero di importazioni che hai per una serie di motivi. Senza alcuna ricerca, la prima cosa che viene in mente è la riduzione del tempo di compilazione.

Si noti che quando si eredita da una class, non si può semplicemente usare una dichiarazione in avanti. Devi importare il file, in modo che la class che stai dichiarando sappia come è definita.

Questo è uno scenario di esempio, in cui abbiamo bisogno di @class.

Considerare se si desidera creare un protocollo all’interno del file di intestazione, che ha un parametro con tipo di dati della stessa class, quindi è ansible utilizzare @class. Ricorda che puoi anche dichiarare i protocolli separatamente, questo è solo un esempio.

 // DroneSearchField.h #import  @class DroneSearchField; @protocol DroneSearchFieldDelegate @optional - (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField; @end @interface DroneSearchField : UITextField @end