@class vs. #import


709

È mia comprensione che si dovrebbe usare una dichiarazione di classe forward nel caso in cui ClassA debba includere un'intestazione ClassB e ClassB debba includere un'intestazione ClassA per evitare inclusioni circolari. Capisco anche che an #importè un semplice in ifndefmodo che una inclusione avvenga solo una volta.

La mia richiesta è questa: quando si usa #importe quando si usa @class? A volte se uso una @classdichiarazione, vedo un avviso comune del compilatore come il seguente:

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

Mi piacerebbe davvero capirlo, piuttosto che rimuovere la @classdichiarazione in avanti e lanciare un #importsilenzio per mettere a tacere gli avvertimenti che il compilatore mi sta dando.


10
La dichiarazione forward dice semplicemente al compilatore: "Ehi, so che sto dichiarando cose che non riconosci, ma quando dico @MyClass, prometto che #importerò nell'implementazione".
JoeCortopassi,

Risposte:


754

Se vedi questo avviso:

avvertenza: il ricevitore "MyCoolClass" è una classe diretta e la relativa @interfaccia potrebbe non esistere

è necessario #importil file, ma è possibile farlo nel file di implementazione (.m) e utilizzare la @classdichiarazione nel file di intestazione.

@class(di solito) non rimuove la necessità di #importfile, sposta semplicemente il requisito più vicino a dove le informazioni sono utili.

Per esempio

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

MyCoolClass *myObject;

Non deve preoccuparsi di altro che non MyCoolClasssia una classe valida e dovrebbe riservare spazio a un puntatore (in realtà, solo un puntatore). Pertanto, nella tua intestazione, è @classsufficiente il 90% delle volte.

Tuttavia, se mai dovessi creare o accedere myObjectai membri, dovrai far sapere al compilatore quali sono questi metodi. A questo punto (presumibilmente nel tuo file di implementazione), dovrai #import "MyCoolClass.h"dire al compilatore ulteriori informazioni oltre a "questa è una classe".


5
Ottima risposta, grazie. Per riferimento futuro: questo si occupa anche di situazioni in cui si @classqualcosa nel vostro .hfile, ma dimentica di #importessa nel .m, tenta di accedere a un metodo in @classoggetto ed, e ottenere gli avvertimenti come: warning: no -X method found.
Tim

24
Un caso in cui dovresti avere bisogno di #import invece di @class è se il file .h include tipi di dati o altre definizioni necessarie per l'interfaccia della tua classe.
Ken Aspeslagh,

2
Un altro grande vantaggio non menzionato qui è la compilazione rapida. Fare riferimento alla risposta di Venkateshwar
MartinMoizard,

@BenGottlieb Non dovrebbe essere in maiuscolo 'm' in "myCoolClass"? Come in "MyCoolClass"?
Basil Bourque,

182

Tre semplici regole:

  • Solo #importla super classe e i protocolli adottati, nei file di intestazione ( .hfile).
  • #importtutte le classi e i protocolli a cui invii messaggi nell'implementazione ( .mfile).
  • Dichiarazioni in avanti per tutto il resto.

Se si inoltra una dichiarazione nei file di implementazione, probabilmente si fa qualcosa di sbagliato.


22
Nei file di intestazione, potresti anche dover importare qualsiasi cosa che definisca un protocollo adottato dalla tua classe.
Tyler,

C'è una differenza nel dichiarare #import nel file di interfaccia h o nel file di implementazione m?
Samuel G,

E #import se usi variabili di istanza della classe
user151019

1
@Mark - Coperto dalla regola # 1, accedi solo agli ivar dalla tua superclasse, anche se in quel momento.
PeyloW,

@Tyler perché non inoltrare la dichiarazione del protocollo?
JoeCortopassi,

110

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

Nella sezione relativa alla definizione di una classe | Class Interface 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 classe. Essendo semplice, evita potenziali problemi che potrebbero derivare dall'importazione di file che importano ancora altri file. Ad esempio, se una classe dichiara una variabile di istanza tipizzata staticamente di un'altra classe e i loro due file di interfaccia si importano a vicenda, nessuna delle due classi può essere compilata correttamente.

Spero che questo possa essere d'aiuto.


48

Se necessario, utilizzare una dichiarazione diretta nel file di intestazione e #importi file di intestazione per tutte le classi che si stanno utilizzando nell'implementazione. In altre parole, hai sempre #importi file che stai utilizzando nella tua implementazione e, se devi fare riferimento a una classe nel tuo file di intestazione, usa anche una dichiarazione diretta.

L' eccezione a ciò è che dovresti #importereditare un protocollo di classe o formale nel file di intestazione (nel qual caso non dovrai importarlo nell'implementazione).


24

La pratica comune è usare @class nei file di intestazione (ma è ancora necessario importare la superclasse) e #import nei file di implementazione. Ciò eviterà eventuali inclusioni circolari e funzionerà.


2
Ho pensato che #import fosse migliore di #Include in quanto importa solo un'istanza?
Matthew Schinckel,

2
Vero. Non so se si tratta di inclusioni circolari o di ordini errati, ma mi sono avventurato lontano da quella regola (con un'importazione in un'intestazione, le importazioni non erano più necessarie nell'implementazione della sottoclasse), e presto è diventato davvero disordinato. In conclusione, segui questa regola e il compilatore sarà felice.
Steph Thirion,

1
I documenti attuali affermano che #import"è come la direttiva #include di C, tranne per il fatto che si assicura che lo stesso file non venga mai incluso più di una volta." Quindi, in base a ciò, #importsi occupa delle inclusioni circolari, le @classdirettive non sono particolarmente utili.
Eric

24

Un altro vantaggio: compilazione rapida

Se si include un file di intestazione, qualsiasi modifica in esso causa anche la compilazione del file corrente, ma non è così se il nome della classe è incluso come @class name. Ovviamente dovrai includere l'intestazione nel file sorgente


18

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

Risposta semplice: tu #importo #includequando c'è una dipendenza fisica. In caso contrario, è possibile utilizzare le dichiarazioni previsionali ( @class MONClass, struct MONStruct,@protocol MONProtocol ).

Ecco alcuni esempi comuni di dipendenza fisica:

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

A volte se uso una dichiarazione @class, vedo un avviso comune del compilatore come il seguente: "avviso: il ricevitore 'FooController' è una classe diretta e la relativa @interfaccia potrebbe non esistere".

Il compilatore in realtà è molto indulgente in questo senso. Rilascerà suggerimenti (come quello sopra), ma puoi eliminare facilmente il tuo stack se li ignori e non lo fai #importcorrettamente. Anche se dovrebbe (IMO), il compilatore non lo impone. In ARC, il compilatore è più rigoroso 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 e parametro di ritorno siaid . Quindi, dovresti sradicare ogni avviso dai tuoi codebase perché questo dovrebbe essere considerato dipendenza fisica. Ciò è analogo alla chiamata di una funzione C che non è dichiarata. Con C, si presume che i parametri siano int.

Il motivo per cui si preferirebbero le dichiarazioni a termine è che è possibile ridurre i tempi di costruzione in base a fattori poiché esiste una dipendenza minima. Con le dichiarazioni forward, il compilatore vede che c'è un nome e può analizzare e compilare correttamente il programma senza vedere la dichiarazione di classe 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 'più di tempo assicurandoti che tutte le intestazioni di cui hai bisogno siano visibili ad ogni traduzione di conseguenza, ma ciò ripaga rapidamente in tempi di costruzione ridotti (supponendo che il tuo progetto non sia piccolo).

Se usi #importo #includeinvece, stai lanciando molto più lavoro sul compilatore di quanto sia necessario. Stai anche introducendo dipendenze complesse dell'intestazione. Puoi paragonarlo a un algoritmo a forza bruta. Quando tu#import stai trascinando tonnellate di informazioni non necessarie, che richiedono molta memoria, I / O del disco e CPU per analizzare e compilare le fonti.

ObjC è abbastanza vicino all'ideale per un linguaggio basato su C per quanto riguarda la dipendenza perché i NSObjecttipi non sono mai valori -NSObject tipi sono sempre puntatori contati come riferimento. In questo modo puoi cavartela con tempi di compilazione incredibilmente rapidi se strutturi le dipendenze del tuo programma in modo appropriato e in avanti, ove possibile, perché la dipendenza fisica è molto ridotta. È inoltre possibile dichiarare le proprietà nelle estensioni di classe per ridurre ulteriormente la dipendenza. Questo è un enorme vantaggio per i sistemi di grandi dimensioni: sapresti la differenza che fa se avessi mai sviluppato una base di codice C ++ di grandi dimensioni.

Pertanto, la mia raccomandazione è di usare i forward dove possibile, e quindi #importdove c'è dipendenza fisica. Se vedi l'avvertimento o altro che implica dipendenza fisica, correggili tutti. La correzione è #importnel file di implementazione.

Mentre costruisci librerie, classificherai probabilmente alcune interfacce come gruppo, nel qual caso dovresti #importquella libreria in cui viene introdotta la dipendenza fisica (ad es #import <AppKit/AppKit.h>.). Questo può introdurre dipendenza, ma i manutentori della libreria possono spesso gestire le dipendenze fisiche per te, se necessario - se introducono una funzionalità, possono minimizzare l'impatto che ha sulle tue build.


A proposito bello sforzo per spiegare le cose. .ma sembrano piuttosto complessi.
Ajay Sharma

NSObject types are never values -- NSObject types are always reference counted pointers.non del tutto vero. I blocchi gettano una scappatoia nella tua risposta, solo dicendo.
Richard J. Ross III,

@ RichardJ.RossIII ... e GCC permette di dichiarare e usare valori, mentre il clang lo proibisce. e, naturalmente, ci deve essere un valore dietro il puntatore.
justin

11

Vedo molto "Fallo in questo modo" 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 non usi l'eredità. In tal caso, #importerai più volte per una singola @class. Quindi devi ricordare di rimuovere da più file diversi se all'improvviso decidi di non aver 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 compilazione delle prestazioni non è un problema. Se così fosse, non saremmo #importare Cocoa / Cocoa.h o simili in quasi tutti i file di intestazione che abbiamo.


1
vedi la risposta di Abizem sopra per un esempio dalla documentazione del perché dovresti farlo. La sua programmazione difensiva per quando hai due intestazioni di classe che si importano a vicenda con variabili di istanza dell'altra classe.
jackslash,

7

se lo facciamo

@interface Class_B : Class_A

significa 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 oggetto e usare la sua funzione e variabili).


5

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

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

sommario dell'articolo

importa nei file di intestazione:

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

importazioni nei file di implementazione:

  • Elimina le #import che non vengono utilizzate.
  • Se un metodo delega a un altro oggetto e restituisce ciò che restituisce, prova a dichiarare in avanti quell'oggetto invece di #importarlo.
  • Se l'inclusione di un modulo ti obbliga a includere livello dopo livello di dipendenze successive, potresti avere un insieme di classi che vuole diventare una libreria. Costruiscilo come una libreria separata con un'intestazione master, in modo che tutto possa essere portato come un singolo blocco predefinito.
  • Troppe #import? Hai una grande classe.

3

Quando mi 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 secondarie nel mio progetto), le dichiaro tramite forward class.


3

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

Il tuo primo pensiero è probabilmente #importquello.
Ciò può causare problemi in alcuni casi.

Ad esempio se si implementano un gruppo di metodi C nel file di intestazione, o strutture, o qualcosa di simile, perché non dovrebbero essere importati più volte.

Quindi puoi dire al compilatore con @class:

So che non conosci quella classe, ma esiste. Verrà importato o implementato altrove

Fondamentalmente dice al compilatore di chiudere e compilare, anche se non è sicuro se questa classe verrà mai implementata.

Di solito utilizzare #importnel .m e @classnei .h file.


0

Inoltra la dichiarazione solo per impedire al compilatore di mostrare errori.

il compilatore saprà che esiste una classe con il nome che hai usato nel tuo file header per dichiarare.


Potresti essere un po 'più specifico?
Sam Spencer,

0

Il compilatore si lamenterà solo se intendi utilizzare quella classe in modo tale che il compilatore debba conoscerne l'implementazione.

Ex:

  1. Questo potrebbe essere come se tu ne derivassi la classe o
  2. Se hai un oggetto di quella classe come variabile membro (anche se raro).

Non si lamenterà se lo userete solo come puntatore. Ovviamente, dovrai #importarlo nel file di implementazione (se stai creando un'istanza di un oggetto di quella classe) poiché deve conoscere il contenuto della classe per creare un'istanza di un oggetto.

NOTA: #import non è uguale a #include. Ciò significa che non esiste nulla chiamato importazione circolare. import è una specie di richiesta per il compilatore di cercare un determinato file per alcune informazioni. Se tali informazioni sono già disponibili, il compilatore le ignora.

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

Quando usare @class

Usa @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 classe. Casi in cui potresti non avere ancora un'intestazione per quella classe.

Un esempio di ciò potrebbe essere che stai scrivendo due librerie. Una classe, chiamiamola A, esiste in una libreria. Questa libreria include un'intestazione della seconda libreria. Quell'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, i progressi della libreria 2 sono bloccati.


0

Pensa a @class come a dire al compilatore "fidati di me, questo esiste".

Pensa a #import come copia-incolla.

Vuoi ridurre al minimo il numero di importazioni che hai per una serie di motivi. Senza alcuna ricerca, la prima cosa che viene in mente è che riduce i tempi di compilazione.

Nota che quando erediti da una classe, non puoi semplicemente usare una dichiarazione diretta. Devi importare il file, in modo che la classe che stai dichiarando sappia come è definita.


0

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 classe, quindi è possibile utilizzare @class. Ricorda che puoi anche dichiarare i protocolli separatamente, questo è solo un esempio.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
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.