@class vs. #import


709

Наскільки я розумію, що слід використовувати заяву прямого класу у випадку, якщо ClassA повинен включати заголовок ClassB, а ClassB повинен включати заголовок ClassA, щоб уникнути кругових включень. Я також розумію, що анкета #importпроста, ifndefтак що включення трапляється лише один раз.

Мій запит такий: Коли використовується одне #importі коли одне @class? Іноді, якщо я використовую @classдекларацію, я бачу загальне попередження компілятора, таке як:

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

Дуже хотілося б це зрозуміти, порівняно з просто видаленням @classвперед-декларації та введенням, #importщоб заглушити попередження, які мені дає компілятор.


10
Передача декларації просто каже компілятору: "Ей, я знаю, що я декларую речі, які ви не розпізнаєте, але коли я скажу @MyClass, я обіцяю, що я # імпортуватиму це у реалізацію".
JoeCortopassi

Відповіді:


754

Якщо ви бачите це попередження:

попередження: приймач "MyCoolClass" - це клас прямого перебігу, і відповідний @interface може не існувати

вам потрібен #importфайл, але ви можете зробити це у вашому файлі реалізації (.m) та використовувати @classдекларацію у вашому файлі заголовка.

@classне (зазвичай) не знімає потреби у #importфайлах, вона просто переміщує вимогу вниз ближче до того, де інформація корисна.

Наприклад

Якщо ви скажете @class MyCoolClass, компілятор знає, що він може побачити щось на зразок:

MyCoolClass *myObject;

Він не повинен турбуватися ні про що, окрім MyCoolClassдійсного класу, і він повинен залишити місце для вказівника на нього (дійсно, просто вказівник). Таким чином, у вашому заголовку @classвистачає 90% часу.

Однак якщо вам коли-небудь потрібно створити або отримати доступ myObjectдо членів, вам потрібно повідомити компілятору, що це за методи. На цьому етапі (імовірно, у вашому файлі реалізації) вам потрібно #import "MyCoolClass.h"буде повідомити компілятору додаткову інформацію, окрім як "це клас".


5
Чудова відповідь, дякую. Для довідки в майбутньому: це також має справу з ситуаціями , коли ви @classщо - то в вашому .hфайл, але забув #importйому в ом, намагаєтеся отримати доступ до методу на @classоб'єкті ед, і отримати попередження , як: warning: no -X method found.
Тім

24
Випадок, коли вам потрібно буде #import замість @class, якщо файл .h містить типи даних або інші визначення, необхідні для інтерфейсу вашого класу.
Ken Aspeslagh

2
Ще одна велика перевага, яка не згадується тут, - це швидке складання. Будь ласка, зверніться до відповіді Венкатешвар
MartinMoizard

@BenGottlieb Чи не повинен цей "m" у "myCoolClass" бути великим регістром? Як у "MyCoolClass"?
Василь Бурк

182

Три простих правила:

  • Тільки #importсуперклас і прийняті протоколи у заголовкових файлах ( .hфайлах).
  • #importвсі класи та протоколи ви надсилаєте повідомлення в реалізацію ( .mфайли).
  • Пересилайте декларації для всього іншого.

Якщо ви передаєте декларацію у файли реалізації, то, ймовірно, ви робите щось не так.


22
У файлах заголовків вам також може знадобитися #import все, що визначає протокол, який приймає ваш клас.
Тайлер

Чи є різниця в оголошенні #import у h-інтерфейсному файлі або файлі реалізації m?
Самуель Г

І #import, якщо ви використовуєте змінні екземпляри з класу
user151019

1
@Mark - Поширюється за правилом №1, доступ до ivars від вашого суперкласу, якщо навіть тоді.
PeyloW

@Tyler чому б не переслати декларацію протоколу?
JoeCortopassi

110

Подивіться документацію щодо мови програмування Objective-C на АЦП

У розділі «Визначення класу» Інтерфейс класу описує, чому це робиться:

Директива @class мінімізує кількість коду, що бачиться компілятором і лінкером, і тому є найпростішим способом дати попереднє оголошення імені класу. Будучи простим, він дозволяє уникнути можливих проблем, які можуть виникнути при імпорті файлів, які імпортують ще інші файли. Наприклад, якщо один клас оголошує статично типізовану змінну екземпляра іншого класу, а два їх файли інтерфейсу імпортують один одного, жоден клас не може скластися правильно.

Я сподіваюся, що це допомагає.


48

Якщо потрібно, використовуйте декларацію вперед у файлі #importзаголовка та файли заголовків для будь-яких класів, які ви використовуєте в реалізації. Іншими словами, ви завжди #importвикористовуєте файли, які ви використовуєте у своїй реалізації, і якщо вам потрібно посилатися на клас у файлі заголовка, використовуйте також пряму декларацію.

Виняток в тому , що ви повинні #importклас або формальний протокол ви успадковує в файлі заголовка (в цьому випадку вам не потрібно імпортувати його в реалізації).


24

Поширена практика - це використання @class у файлах заголовків (але вам все одно потрібно # імпортувати суперклас) та #import у файли реалізації. Це дозволить уникнути будь-яких кругових включень, і це просто працює.


2
Я думав, що #import кращий, ніж #Include, що він імпортує лише один екземпляр?
Метью Шінккель

2
Правда. Не знаю, чи йдеться про кругові включення чи неправильне впорядкування, але я відмовився від цього правила (при одному імпорті в заголовку імпорт більше не потрібен у впровадженні підкласу), і незабаром він став справді безладним. Підсумок, дотримуйтесь цього правила, і компілятор буде радий.
Стеф Тіріон

1
В даний час документи говорять , що #import«подібно директиві #include мови C, за винятком того, що вона переконується , що той же файл ніколи не включається більш ніж один раз.» Отже, відповідно до цього #importйдеться про кругові включення, @classдирективи в цьому особливо не допомагають.
Ерік

24

Ще одна перевага: Швидке складання

Якщо ви включите файл заголовка, будь-яка зміна в ньому спричиняє збір поточного файлу, але це не так, якщо ім'я класу включено як @class name. Звичайно, вам потрібно буде включити заголовок у вихідний файл


18

Мій запит такий. Коли використовується #import і коли використовується @class?

Проста відповідь: Ви #importабо #includeколи є фізична залежність. В іншому випадку, можна використовувати вперед декларацій ( @class MONClass, struct MONStruct, @protocol MONProtocol).

Ось кілька поширених прикладів фізичної залежності:

  • Будь-яке значення C або C ++ (вказівник або посилання не є фізичною залежністю). Якщо у вас є CGPointivar або властивість, компілятору необхідно переглянути декларацію CGPoint.
  • Ваш суперклас.
  • Метод, який ви використовуєте.

Іноді, якщо я використовую декларацію @class, я бачу загальне попередження компілятора, таке як: "попередження: приймач 'FooController' - це клас прямого переходу, і відповідний @ інтерфейс може не існувати."

Насправді компілятор дуже м'який. Він видасть підказки (наприклад, описаний вище), але ви можете легко виправити свій стек, якщо проігнорувати їх і не виконати #importналежним чином. Хоча це повинно (IMO), компілятор цього не виконує. У ARC компілятор є більш суворим, оскільки він відповідає за підрахунок посилань. Що відбувається - компілятор відпадає від за замовчуванням, коли він стикається з невідомим методом, який ви викликаєте. Кожне повернене значення та параметр вважається рівним id. Таким чином, вам слід викорінювати кожне попередження зі своїх кодових баз, оскільки це слід вважати фізичною залежністю. Це аналогічно виклику функції C, яка не оголошується. З С параметри приймаються такими, що є int.

Причина, за яку ви віддаєте перевагу переадресаційним деклараціям, полягає в тому, що ви можете скоротити час складання на фактори, оскільки існує мінімальна залежність. За допомогою прямого декларування компілятор бачить, що є ім'я, і ​​може правильно розбирати та компілювати програму, не бачачи декларації класу або всіх її залежностей, коли фізичної залежності немає. Чисті конструкції займають менше часу. Поступові нарощування займають менше часу. Звичайно, ви витратите трохи більше часу, переконавшись, що всі необхідні заголовки будуть видимими для кожного перекладу як наслідок, але це окупається в скорочені терміни складання (припустимо, що ваш проект не крихітний).

Якщо ви використовуєте #importабо #includeзамість цього, ви кидаєте набагато більше роботи на компілятор, ніж потрібно. Ви також вводите складні залежності заголовків. Це можна порівняти з алгоритмом грубої сили. Коли ви #import, ви перетягуєте тонни непотрібної інформації, що вимагає багато пам’яті, дискового вводу / виводу та процесора для розбору та компіляції джерел.

ObjC досить близький до ідеального для мови, що базується на C, залежно від залежності, оскільки NSObjectтипи ніколи не є значеннями - NSObjectтипи завжди посилаються на лічильник покажчиків. Таким чином, ви можете піти з неймовірно швидким часом компіляції, якщо структурувати залежність своєї програми належним чином і вперед, де це можливо, тому що фізична залежність потрібна дуже мало. Ви також можете оголосити властивості в розширеннях класів, щоб додатково мінімізувати залежність. Це величезний бонус для великих систем - ви б знали різницю, якщо ви коли-небудь розробляли велику C ++ базу даних.

Тому моя рекомендація - використовувати вперед, де це можливо, а потім туди, #importде є фізична залежність. Якщо ви бачите попередження чи інше, що передбачає фізичну залежність - виправте їх усі. Виправлення полягає #importу вашому файлі реалізації.

Коли ви будуєте бібліотеки, ви, швидше за все, класифікуєте деякі інтерфейси як групу; у цьому випадку ви використовуєте #importту бібліотеку, де введена фізична залежність (наприклад #import <AppKit/AppKit.h>). Це може ввести залежність, але бібліотечні працівники часто можуть обробляти фізичні залежності для вас за потреби - якщо вони вводять функцію, вони можуть мінімізувати вплив, який вона має на ваші побудови.


До речі, приємні зусилля для пояснення речей. .але вони здаються досить складними.
Аджай Шарма

NSObject types are never values -- NSObject types are always reference counted pointers.не зовсім правда. Блоки кидають лазівку у вашій відповіді, просто кажучи.
Річард Дж. Росс III

@ RichardJ.RossIII ... і GCC дозволяє оголошувати і використовувати значення, в той час як clang забороняє це. і звичайно, за вказівником повинно бути значення.
Джастін

11

Я бачу багато "Зроби так", але не бачу відповідей на "Чому?"

Отже: чому ви маєте @class у своєму заголовку та #import лише у своїй реалізації? Ви подвоюєте свою роботу, доводиться весь час @class і #import . Якщо ви не скористаєтеся спадщиною. У такому випадку ви будете # імпортувати кілька разів для одного @class. Тоді вам доведеться пам’ятати про видалення з декількох різних файлів, якщо раптом вирішите, що вам більше не потрібен доступ до декларації.

Імпорт одного файлу кілька разів не є проблемою через характер #import. Компіляція продуктивності теж насправді не проблема. Якби це, ми не мали б # імпортувати какао / какао.h або подібні у майже кожному файлі заголовка.


1
див. відповідь Абізема вище для прикладу з документації, чому ви повинні це робити. Його захисне програмування, коли у вас є два заголовки класу, які імпортують один одного із змінними екземпляра іншого класу.
відсіч

7

якщо ми це зробимо

@interface Class_B : Class_A

означає, що ми успадковуємо Class_A в Class_B, в Class_B ми можемо отримати доступ до всіх змінних class_A.

якщо ми це робимо

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

тут ми говоримо, що ми використовуємо Class_A у нашій програмі, але якщо ми хочемо використовувати змінні Class_A у Class_B, нам потрібно #import Class_A у .m файл (створити об’єкт та використовувати його функцію та змінні).


5

для отримання додаткової інформації про залежність від файлів & #import & @class перевірте це:

http://qualitycoding.org/file-dependency/ це хороша стаття

резюме статті

імпорт у файли заголовків:

  • # імпортуйте суперклас, який ви успадковуєте, та протоколи, які ви реалізуєте.
  • Вперед - оголосити все інше (якщо тільки воно не виходить із рамки з головним заголовком).
  • Спробуйте усунути всі інші #імпорти.
  • Декларуйте протоколи у власних заголовках, щоб зменшити залежності.
  • Занадто багато вперед декларацій? У вас великий клас.

імпорт у файли реалізації:

  • Усуньте чіткий # імпорт, який не використовується.
  • Якщо метод делегує інший об’єкт і повертає те, що він отримує назад, спробуйте переслати його вперед, а не # імпортувати його.
  • Якщо включення модуля змушує вас включати рівень за рівнем послідовних залежностей, у вас може бути набір класів, який хоче стати бібліотекою. Створіть його як окрему бібліотеку з головним заголовком, тому все можна занести як єдиний попередньо зведений фрагмент.
  • Занадто багато # імпортів? У вас великий клас.

3

Коли я розвиваюся, маю на увазі лише три речі, які ніколи не викликають у мене жодних проблем.

  1. Імпортуйте суперкласи
  2. Імпорт батьківських класів (коли у вас є діти та батьки)
  3. Імпортуйте класи поза вашим проектом (наприклад, у рамки та бібліотеки)

Для всіх інших класів (підкласи та дочірні класи в самому проекті) я декларую їх за допомогою прямого класу.


3

Якщо ви спробуєте оголосити змінну або властивість у вашому файлі заголовка, яку ви ще не імпортували, ви отримаєте помилку про те, що компілятор не знає цього класу.

Ваша перша думка - це, мабуть, #importсаме так.
Це може спричинити проблеми в деяких випадках.

Наприклад, якщо ви реалізуєте купу C-методів у файлі заголовка, або структури, або щось подібне, тому що вони не повинні бути імпортовані кілька разів.

Тому ви можете сказати компілятору за допомогою @class:

Я знаю, ви не знаєте цього класу, але він існує. Він буде імпортований або впроваджений в іншому місці

Це в основному говорить компілятору закрити і компілювати, хоча це не точно, чи буде цей клас реалізований коли-небудь.

Зазвичай ви будете використовувати #importв .m і @classв .h файлах.


0

Пересилаємо декларацію просто для того, щоб компілятор не відображав помилки.

компілятор буде знати, що існує клас з ім'ям, яке ви використовували у своєму заголовковому файлі для декларування.


Чи можете ви бути трохи більш конкретним?
Сем Спенсер

0

Компілятор буде скаржитися лише в тому випадку, якщо ви збираєтеся використовувати цей клас таким чином, що компілятору необхідно знати його реалізацію.

Наприклад:

  1. Це може бути, як якщо ви збираєтеся отримати свій клас з нього або
  2. Якщо ви будете мати об'єкт цього класу як змінну члена (хоча це рідко).

Він не поскаржиться, якщо ви просто збираєтесь використовувати його як вказівник. Звичайно, вам доведеться # імпортувати його у файл реалізації (якщо ви створюєте інстанцію об'єкта цього класу), оскільки він повинен знати вміст класу для інстанції об'єкта.

ПРИМІТКА: #import не є тим самим, як #include. Це означає, що немає нічого, що називається круговим імпортом. import - це своєрідний запит компілятора на пошук певної інформації для певної інформації. Якщо ця інформація вже доступна, компілятор її ігнорує.

Просто спробуйте це, імпортуйте Ах в Бх і Бх в А. Не буде проблем і скарг, і це також буде добре.

Коли використовувати @class

Ви використовуєте @class лише в тому випадку, якщо навіть не хочете імпортувати заголовок у свій заголовок. Це може бути випадок, коли вам навіть байдуже знати, яким буде цей клас. Випадки, коли у вас ще може навіть немає заголовка для цього класу.

Прикладом цього може бути те, що ви пишете дві бібліотеки. Один клас, давайте назвати його А, існує в одній бібліотеці. Ця бібліотека містить заголовок другої бібліотеки. Цей заголовок може мати вказівник на A, але знову може не потрібно його використовувати. Якщо бібліотека 1 ще не доступна, бібліотека B не буде заблокована, якщо ви використовуєте @class. Але якщо ви хочете імпортувати Ах, то прогрес бібліотеки 2 заблокований.


0

Подумайте про @class як про те, щоб сказати компілятору "повір мені, це існує".

Подумайте про #import як копіювальну пасту.

Ви хочете мінімізувати кількість імпорту у вас з кількох причин. Без будь-яких досліджень перше, що спадає на думку, це скорочення часу на компіляцію.

Зауважте, що, успадковуючи клас, ви не можете просто використовувати пряму декларацію. Вам потрібно імпортувати файл, щоб клас, який ви декларуєте, знав, як його визначено.


0

Це приклад сценарію, де нам потрібен @class.

Подумайте, якщо ви хочете створити протокол у файлі заголовка, у якого є параметр із типом даних того ж класу, ви можете використовувати @class. Пам'ятайте, що ви також можете декларувати протоколи окремо, це лише приклад.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.