@class vs. #import

709

Rozumiem, że należy użyć deklaracji klasy forward w przypadku, gdy ClassA musi zawierać nagłówek ClassB, a ClassB musi zawierać nagłówek ClassA, aby uniknąć okrągłych inkluzji. Rozumiem również, że an #importjest proste, ifndefwięc dołączenie zdarza się tylko raz.

Moje pytanie brzmi: kiedy się używa, #importa kiedy się używa @class? Czasami jeśli używam @classdeklaracji, widzę wspólne ostrzeżenie kompilatora, takie jak następujące:

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

Naprawdę chciałbym to zrozumieć, zamiast po prostu usunąć @classdeklarację przesyłania dalej i wrzucić #importw celu wyciszenia ostrzeżeń, które daje mi kompilator.

Coocoo4Cocoa
źródło
10
Deklaracja przekazania mówi kompilatorowi: „Hej, wiem, że deklaruję rzeczy, których nie rozpoznajesz, ale kiedy mówię @MyClass, obiecuję, że #importuję to w implementacji”.
JoeCortopassi

Odpowiedzi:

754

Jeśli zobaczysz to ostrzeżenie:

ostrzeżenie: odbiornik „MyCoolClass” jest klasą przekazującą i odpowiedni @ interfejs może nie istnieć

potrzebujesz #importtego pliku, ale możesz to zrobić w pliku implementacji (.m) i użyć @classdeklaracji w pliku nagłówkowym.

@classnie usuwa (zwykle) potrzeby #importplików, po prostu przenosi wymaganie w dół do miejsca, w którym informacje są przydatne.

Na przykład

Jeśli powiesz @class MyCoolClass, kompilator wie, że może zobaczyć coś takiego:

MyCoolClass *myObject;

Nie musi się martwić o nic innego niż MyCoolClasspoprawną klasę i powinien zarezerwować miejsce na wskaźnik do niej (tak naprawdę tylko wskaźnik). Zatem w nagłówku @classwystarcza 90% czasu.

Jednak jeśli kiedykolwiek będziesz chciał utworzyć myObjectczłonków lub uzyskać do nich dostęp , musisz poinformować kompilator o tym, jakie są te metody. W tym momencie (przypuszczalnie w pliku implementacyjnym) będziesz musiał #import "MyCoolClass.h"przekazać kompilatorowi dodatkowe informacje poza „to jest klasa”.

Ben Gottlieb
źródło
5
Świetna odpowiedź, dzięki. Dla porównania w przyszłości: ten zajmuje się również sytuacje, w których @classcoś w .hpliku, ale zapomnij o #importnim w .m, próby uzyskania dostępu do metody na @classobiekcie ED, i dostać ostrzeżenia jak: warning: no -X method found.
Tim
24
Przypadek, w którym musisz #import zamiast @class, ma miejsce, jeśli plik .h zawiera typy danych lub inne definicje niezbędne dla interfejsu twojej klasy.
Ken Aspeslagh
2
Kolejną wielką zaletą nie wymienioną tutaj jest szybka kompilacja. Proszę odnieść się do odpowiedzi Venkateshwar
MartinMoizard
@BenGottlieb Czy nie powinno być „m” w „myCoolClass” wielkimi literami? Jak w „MyCoolClass”?
Basil Bourque
182

Trzy proste zasady:

  • Tylko #importsuperklasa i przyjęte protokoły w plikach nagłówkowych ( .hplikach).
  • #importwszystkie klasy i protokoły, do których wysyłane są wiadomości w implementacji ( .mpliki).
  • Prześlij deklaracje dotyczące wszystkiego innego.

Jeśli przekażesz deklarację w plikach implementacyjnych, prawdopodobnie zrobisz coś złego.

PeyloW
źródło
22
W plikach nagłówkowych może być konieczne #importowanie wszystkiego, co definiuje protokół przyjęty przez twoją klasę.
Tyler
Czy istnieje różnica w deklarowaniu #import w pliku interfejsu h lub pliku implementacji m?
Samuel G
I #import, jeśli używasz zmiennych instancji z klasy
user151019
1
@ Mark - objęty regułą nr 1, uzyskuje dostęp do ivars z nadklasy, jeśli nawet wtedy.
PeyloW
@Tyler, dlaczego nie przesłać deklaracji protokołu?
JoeCortopassi
110

Zobacz dokumentację języka programowania Objective-C w ADC

W sekcji dotyczącej definiowania klasy | Interfejs klasy opisuje, dlaczego tak się dzieje:

Dyrektywa @class minimalizuje ilość kodu widzianego przez kompilator i linker, a zatem jest najprostszym sposobem na podanie w przód deklaracji nazwy klasy. Mówiąc prosto, pozwala uniknąć potencjalnych problemów związanych z importowaniem plików, które importują jeszcze inne pliki. Na przykład, jeśli jedna klasa deklaruje statycznie zmienną instancji innej klasy, a ich dwa pliki interfejsu importują się nawzajem, żadna klasa nie może się poprawnie skompilować.

Mam nadzieję, że to pomoże.

Abizern
źródło
48

W razie potrzeby użyj deklaracji przekazywania w pliku nagłówkowym oraz #importplików nagłówkowych dla wszystkich klas używanych w implementacji. Innymi słowy, zawsze używasz #importplików, których używasz w swojej implementacji, a jeśli potrzebujesz odwoływać się do klasy w pliku nagłówkowym, użyj również deklaracji przesyłania dalej.

Wyjątkiem jest to, że należy #importklasą lub formalny protokół jesteś dziedziczenie z nagłówka w pliku (w takim przypadku nie będzie musiała importować go w realizacji).

Marc Charbonneau
źródło
24

Powszechną praktyką jest używanie @class w plikach nagłówkowych (ale nadal trzeba #importować nadklasę) i #import w plikach implementacyjnych. Pozwoli to uniknąć okrągłych wtrąceń i po prostu działa.

Steph Thirion
źródło
2
Myślałem, że #import był lepszy niż #Włącz, ponieważ importuje tylko jedną instancję?
Matthew Schinckel
2
Prawdziwe. Nie wiem, czy chodzi o inkluzje kołowe, czy o niepoprawne porządkowanie, ale odważyłem się od tej reguły (z jednym importem w nagłówku, importy nie były już potrzebne w implementacji podklasy) i wkrótce zrobiło się naprawdę bałagan. Podsumowując, przestrzegaj tej zasady, a kompilator będzie szczęśliwy.
Steph Thirion
1
Te obecne docs powiedzieć, że #import„jest jak dyrektywy #include C, z wyjątkiem, że to sprawia, że na pewno, że ten sam plik nie jest zawarty więcej niż jeden raz.” Tak więc zgodnie z tym #importdba się o inkluzje kołowe, @classdyrektywy nie pomagają w tym szczególnie.
Eric
24

Kolejna zaleta: Szybka kompilacja

Jeśli dołączasz plik nagłówka, każda zmiana spowoduje, że bieżący plik również się skompiluje, ale nie dzieje się tak, jeśli nazwa klasy jest uwzględniona jako @class name. Oczywiście musisz dołączyć nagłówek do pliku źródłowego

kratka wentylacyjna
źródło
18

Moje zapytanie jest takie. Kiedy używa się #import, a kiedy używa @class?

Prosta odpowiedź: Ty #importlub #includekiedy istnieje fizyczna zależność. W przeciwnym razie, należy użyć do przodu deklaracji ( @class MONClass, struct MONStruct, @protocol MONProtocol).

Oto kilka typowych przykładów fizycznej zależności:

  • Dowolna wartość C lub C ++ (wskaźnik lub odwołanie nie jest zależnością fizyczną). Jeśli masz CGPointjako ivar lub właściwość, kompilator będzie musiał zobaczyć deklarację CGPoint.
  • Twoja nadklasa.
  • Metoda, której używasz.

Czasami, jeśli używam deklaracji @class, widzę wspólne ostrzeżenie kompilatora, takie jak: „ostrzeżenie: odbiornik„ FooController ”jest klasą przekazującą i odpowiedni @ interfejs może nie istnieć.”

Kompilator jest pod tym względem bardzo łagodny. Będzie upuszczał podpowiedzi (takie jak powyższe), ale możesz łatwo wyrzucić stos, jeśli je zignorujesz i nie zrobisz tego #importpoprawnie. Chociaż powinien (IMO), kompilator tego nie wymusza. W ARC kompilator jest bardziej rygorystyczny, ponieważ odpowiada za zliczanie referencji. Dzieje się tak, gdy kompilator wraca do stanu domyślnego, gdy napotyka nieznaną metodę, którą wywołujesz. Przyjmowana jest każda zwracana wartość i parametr id. Dlatego należy usunąć każde ostrzeżenie z baz kodu, ponieważ należy to uznać za zależność fizyczną. Jest to analogiczne do wywoływania funkcji C, która nie jest zadeklarowana. W przypadku C zakłada się, że parametry są int.

Powodem, dla którego preferujesz deklaracje forward, jest to, że możesz skrócić czas kompilacji przez czynniki, ponieważ istnieje minimalna zależność. W przypadku deklaracji przesyłania dalej kompilator widzi nazwę i może poprawnie parsować i kompilować program bez wyświetlania deklaracji klasy lub wszystkich jej zależności, gdy nie ma zależności fizycznej. Czyste wersje zajmują mniej czasu. Kompilacje przyrostowe zajmują mniej czasu. Oczywiście skończysz trochę dłużej, upewniając się, że wszystkie potrzebne nagłówki są widoczne w każdym tłumaczeniu, ale szybko się to zwraca (przy założeniu, że twój projekt nie jest mały).

Jeśli używasz #importlub #includezamiast tego, kompilujesz dużo więcej pracy niż jest to konieczne. Wprowadzasz również złożone zależności nagłówka. Możesz to porównać do algorytmu brutalnej siły. Podczas #importprzeciągania ton niepotrzebnych informacji, które wymagają dużej ilości pamięci, dyskowych operacji we / wy i procesora do przeanalizowania i skompilowania źródeł.

ObjC jest bardzo zbliżony do ideału dla języka opartego na języku C, jeśli chodzi o zależność, ponieważ NSObjecttypy nigdy nie są wartościami - NSObjecttypy są zawsze wskaźnikowymi licznikami odniesienia. Dzięki temu możesz uniknąć niesamowicie szybkich czasów kompilacji, jeśli odpowiednio ustrukturyzujesz zależności programu i przekażesz je tam, gdzie to możliwe, ponieważ wymagana jest bardzo niewielka zależność fizyczna. Możesz także zadeklarować właściwości w rozszerzeniach klas, aby dodatkowo zminimalizować zależność. To ogromny bonus dla dużych systemów - znałbyś różnicę, gdybyś kiedykolwiek opracował dużą bazę kodu C ++.

Dlatego zalecam, aby w miarę możliwości używać naprzód, a następnie #importtam, gdzie występuje fizyczna zależność. Jeśli zobaczysz ostrzeżenie lub inne wskazujące na fizyczną zależność - napraw je wszystkie. Poprawka znajduje się #importw pliku implementacji.

Podczas budowania bibliotek prawdopodobnie sklasyfikujesz niektóre interfejsy jako grupę, w którym to przypadku zrobiłbyś #importbibliotekę, w której wprowadzono zależność fizyczną (np #import <AppKit/AppKit.h>.). Może to wprowadzić zależność, ale opiekunowie bibliotek często potrafią obsłużyć fizyczne zależności w zależności od potrzeb - jeśli wprowadzą funkcję, mogą zminimalizować wpływ, jaki ma ona na twoje kompilacje.

justin
źródło
BTW miły wysiłek, aby wyjaśnić rzeczy. . ale wydają się być dość skomplikowane.
Ajay Sharma
NSObject types are never values -- NSObject types are always reference counted pointers.nie do końca prawda. Bloki rzucają lukę w twojej odpowiedzi, mówiąc tylko.
Richard J. Ross III
@ RichardJ.RossIII… a GCC pozwala deklarować i wykorzystywać wartości, podczas gdy clang zabrania ich. i oczywiście za wskaźnikiem musi znajdować się wartość.
justin
11

Widzę dużo „Zrób to w ten sposób”, ale nie widzę żadnych odpowiedzi na „Dlaczego?”

Więc: dlaczego powinieneś @class w nagłówku i #import tylko w swojej implementacji? Podwajasz swoją pracę, cały czas @class i #import. Chyba że skorzystasz z dziedziczenia. W takim przypadku będziesz #importować wiele razy dla jednej @ klasy. Następnie musisz pamiętać o usunięciu z wielu różnych plików, jeśli nagle zdecydujesz, że nie potrzebujesz już dostępu do deklaracji.

Wielokrotne importowanie tego samego pliku nie stanowi problemu ze względu na naturę #import. Kompilacja wydajności nie jest tak naprawdę problemem. Gdyby tak było, nie byłoby #importowania Cocoa / Cocoa.h lub podobnego w prawie każdym pliku nagłówkowym.

Bruce Goodwin
źródło
1
patrz odpowiedź Abizem powyżej, aby znaleźć przykład z dokumentacji, dlaczego powinieneś to zrobić. Programowanie obronne, gdy masz dwa nagłówki klas, które importują się nawzajem za pomocą zmiennych instancji drugiej klasy.
jackslash
7

jeśli to zrobimy

@interface Class_B : Class_A

oznacza to, że dziedziczymy Class_A do Class_B, w Class_B możemy uzyskać dostęp do wszystkich zmiennych klasy_A.

jeśli to robimy

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

tutaj mówimy, że używamy Class_A w naszym programie, ale jeśli chcemy użyć zmiennych Class_A w Class_B, musimy #importować Class_A w pliku .m (stworzyć obiekt i użyć jego funkcji i zmiennych).

Anshuman Mishra
źródło
5

Aby uzyskać dodatkowe informacje o zależnościach plików oraz #import i @class, sprawdź to:

http://qualitycoding.org/file-dependencies/ to dobry artykuł

streszczenie artykułu

importuje w plikach nagłówkowych:

  • #importuj dziedziczoną superklasę i wdrażane protokoły.
  • Deklaruj do przodu wszystko inne (chyba że pochodzi z frameworka z nagłówkiem głównym).
  • Spróbuj wyeliminować wszystkie inne #import.
  • Deklaruj protokoły we własnych nagłówkach, aby zmniejszyć zależności.
  • Za dużo deklaracji forward? Masz dużą klasę.

importuje z plików implementacyjnych:

  • Wyeliminuj cruft #imports, które nie są używane.
  • Jeśli metoda deleguje do innego obiektu i zwraca to, co otrzymuje, spróbuj zadeklarować dalej ten obiekt zamiast #importować go.
  • Jeśli dołączenie modułu zmusza do uwzględnienia kolejnych zależności między poziomami, możesz mieć zestaw klas, które chcą zostać biblioteką. Zbuduj go jako osobną bibliotekę z nagłówkiem głównym, dzięki czemu wszystko może zostać wprowadzone jako pojedyncza, wstępnie zbudowana porcja.
  • Za dużo #importów? Masz dużą klasę.
Homam
źródło
3

Kiedy się rozwijam, mam na myśli tylko trzy rzeczy, które nigdy nie powodują żadnych problemów.

  1. Importuj superklasy
  2. Importuj zajęcia dla rodziców (gdy masz dzieci i rodziców)
  3. Zaimportuj klasy poza projekt (np. W ramach i bibliotekach)

Dla wszystkich innych klas (podklas i klas potomnych w moim projekcie self), deklaruję je poprzez klasę forward.

Constantino Tsarouhas
źródło
3

Jeśli spróbujesz zadeklarować zmienną lub właściwość w pliku nagłówkowym, którego jeszcze nie zaimportowałeś, pojawi się błąd informujący, że kompilator nie zna tej klasy.

Prawdopodobnie twoja pierwsza myśl #import.
W niektórych przypadkach może to powodować problemy.

Na przykład, jeśli zaimplementujesz kilka metod C w pliku nagłówkowym, strukturach lub czegoś podobnego, ponieważ nie należy ich importować wiele razy.

Dlatego możesz powiedzieć kompilatorowi @class:

Wiem, że nie znasz tej klasy, ale ona istnieje. Zostanie zaimportowany lub wdrożony gdzie indziej

Zasadniczo mówi kompilatorowi, aby zamknął się i skompilował, nawet jeśli nie jest pewne, czy ta klasa kiedykolwiek zostanie zaimplementowana.

Zazwyczaj użyć #importw .m i @classw .h plików.

IluTov
źródło
0

Przekaż deklarację tylko po to, aby kompilator nie wyświetlał błędu.

kompilator będzie wiedział, że istnieje klasa o nazwie użytej w pliku nagłówkowym do zadeklarowania.

karthick
źródło
Czy możesz być trochę bardziej szczegółowy?
Sam Spencer,
0

Kompilator będzie narzekał tylko, jeśli zamierzasz używać tej klasy w taki sposób, że kompilator musi znać jej implementację.

Dawny:

  1. Może to być tak, jakbyś wyprowadził z niej swoją klasę
  2. Jeśli masz mieć obiekt tej klasy jako zmienną składową (choć rzadką).

Nie będzie narzekać, jeśli zamierzasz użyć go jako wskaźnika. Oczywiście będziesz musiał #importować go do pliku implementacji (jeśli tworzysz instancję obiektu tej klasy), ponieważ musi znać zawartość klasy, aby utworzyć instancję obiektu.

UWAGA: #import to nie to samo co #include. Oznacza to, że nie ma nic takiego jak import cykliczny. import jest rodzajem żądania kompilatora, aby szukał określonego pliku w celu uzyskania pewnych informacji. Jeśli te informacje są już dostępne, kompilator je ignoruje.

Po prostu spróbuj, zaimportuj Ah w Bh i Bh w Ah. Nie będzie żadnych problemów ani skarg, a także zadziała.

Kiedy używać @class

Korzystasz z @class tylko wtedy, gdy nie chcesz nawet importować nagłówka do nagłówka. Może to być przypadek, w którym nawet nie chcesz wiedzieć, czym będzie ta klasa. Przypadki, w których możesz nawet nie mieć nagłówka dla tej klasy.

Przykładem może być pisanie dwóch bibliotek. Jedna klasa, nazwijmy ją A, istnieje w jednej bibliotece. Ta biblioteka zawiera nagłówek z drugiej biblioteki. Ten nagłówek może mieć wskaźnik A, ale znowu może nie być konieczne jego użycie. Jeśli biblioteka 1 nie jest jeszcze dostępna, biblioteka B nie zostanie zablokowana, jeśli użyjesz @class. Ale jeśli chcesz zaimportować Ah, postępy biblioteki 2 są zablokowane.

Deepak GM
źródło
0

Pomyśl o @class jako mówieniu kompilatorowi: „zaufaj mi, to istnieje”.

Pomyśl o #import jak o kopiowaniu i wklejaniu.

Chcesz zminimalizować liczbę importów, które masz z wielu powodów. Bez badań pierwszą rzeczą, która przychodzi na myśl, jest to, że skraca czas kompilacji.

Zauważ, że kiedy dziedziczysz po klasie, nie możesz po prostu użyć deklaracji forward. Musisz zaimportować plik, aby deklarowana klasa wiedziała, jak jest zdefiniowany.

Brandon M.
źródło
0

To przykładowy scenariusz, w którym potrzebujemy @class.

Zastanów się, czy chcesz utworzyć protokół w pliku nagłówkowym, który ma parametr z typem danych tej samej klasy, możesz użyć @class. Pamiętaj, że możesz również deklarować protokoły osobno, to tylko przykład.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
Sujananth
źródło