@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в него, чтобы заглушить предупреждения, которые компилятор дает мне.

Coocoo4Cocoa
источник
10
Форвардное объявление просто говорит компилятору: «Привет, я знаю, что я объявляю вещи, которые вы не узнаете, но когда я говорю @MyClass, я обещаю, что #import это в реализации».
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 содержит типы данных или другие определения, необходимые для интерфейса вашего класса.
Кен Аспеллах
2
Еще одно большое преимущество, не упомянутое здесь, это быстрая компиляция. Пожалуйста, обратитесь к ответу Venkateshwar
MartinMoizard
@BenGottlieb Разве это не должно быть 'm' в "myCoolClass" в верхнем регистре? Как в "MyCoolClass"?
Василий Бурк
182

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

  • Только #importсуперкласс и принятые протоколы в заголовочных файлах ( .hфайлах).
  • #importвсе классы и протоколы, в которые вы отправляете сообщения ( .mфайлы).
  • Форвард декларации для всего остального.

Если вы делаете предварительное объявление в файлах реализации, то вы, вероятно, делаете что-то не так.

PeyloW
источник
22
В заголовочных файлах вам также может понадобиться импортировать все, что определяет протокол, принятый вашим классом.
Тайлер
Есть ли разница в объявлении #import в файле интерфейса h или файле реализации m?
Самуил Дж.
И #import, если вы используете переменные экземпляра из класса
user151019
1
@Mark - распространяется по правилу № 1, только доступ к ivars из вашего суперкласса, если даже тогда.
PeyloW
@ Тайлер, почему бы не переслать объявление протокола?
ДжоКортопасси
110

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

Под разделом по определению класса | Интерфейс класса описывает, почему это делается:

Директива @class минимизирует объем кода, видимого компилятором и компоновщиком, и, следовательно, является самым простым способом дать прямое объявление имени класса. Проще говоря, это позволяет избежать потенциальных проблем, которые могут возникнуть при импорте файлов, импортирующих еще другие файлы. Например, если один класс объявляет статически типизированную переменную экземпляра другого класса, и их два интерфейсных файла импортируют друг друга, ни один класс не может скомпилироваться правильно.

Надеюсь, это поможет.

Abizern
источник
48

При необходимости используйте предварительное объявление в заголовочном файле и #importзаголовочные файлы для любых классов, которые вы используете в реализации. Другими словами, вы всегда#import файлы, которые используете в своей реализации, и если вам нужно сослаться на класс в вашем заголовочном файле, используйте также предварительное объявление.

Исключение в том , что вы должны #importкласс или формальный протокол вы наследующий в файле заголовка (в этом случае вам не нужно импортировать его в реализации).

Марк Шарбонно
источник
24

Обычной практикой является использование @class в заголовочных файлах (но вам все еще нужно #import суперкласса) и #import в файлах реализации. Это позволит избежать любых круговых включений, и это просто работает.

Стеф Тирион
источник
2
Я думал, что #import лучше, чем #Include в том, что он импортирует только один экземпляр?
Мэтью Шинкель
2
Правда. Не знаю, касается ли это циклических включений или неправильного упорядочения, но я отошел от этого правила (с одним импортом в заголовке, импорты больше не были нужны в реализации подкласса), и вскоре это стало действительно грязным. Итог, следуйте этому правилу, и компилятор будет счастлив.
Стеф Тирион
1
В текущих документах говорится, что #import«это похоже на директиву C #include, за исключением того, что она гарантирует, что один и тот же файл никогда не включается более одного раза». Таким образом, в соответствии с этим #importзаботятся о круглых включениях, @classдирективы не особенно помогают с этим.
Эрик
24

Еще одно преимущество: быстрая компиляция

Если вы включаете заголовочный файл, любое изменение в нем также приводит к компиляции текущего файла, но это не тот случай, если имя класса включено как @class name. Конечно, вам нужно будет включить заголовок в исходный файл

вентиляционный
источник
18

Мой запрос таков. Когда каждый использует #import и когда один использует @class?

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

Вот несколько распространенных примеров физической зависимости:

  • Любое значение C или C ++ (указатель или ссылка не является физической зависимостью). Если у вас есть CGPointкак ivar или свойство, компилятор должен увидеть объявлениеCGPoint .
  • Ваш суперкласс.
  • Метод, который вы используете.

Иногда, если я использую объявление @class, я вижу общее предупреждение компилятора, например: «предупреждение: получатель« FooController »является классом пересылки, и соответствующий @interface может не существовать».

Компилятор на самом деле очень снисходителен в этом отношении. Он будет сбрасывать подсказки (например, приведенные выше), но вы можете легко испортить свой стек, если проигнорируете их и не сделаете #importправильно. Хотя это должно (IMO), компилятор не обеспечивает это. В ARC компилятор более строг, потому что он отвечает за подсчет ссылок. Что происходит, так это то, что компилятор возвращается к значению по умолчанию, когда он встречает неизвестный метод, который вы вызываете. Каждое возвращаемое значение и параметр предполагается равным id. Таким образом, вы должны исключить каждое предупреждение из ваших кодовых баз, потому что это следует рассматривать как физическую зависимость. Это аналогично вызову функции C, которая не объявлена. С 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. Если вы не используете наследство. В этом случае вы будете #importing несколько раз для одного @class. Затем вы должны забыть удалить из нескольких разных файлов, если вдруг решите, что вам больше не нужен доступ к объявлению.

Многократный импорт одного и того же файла не является проблемой из-за природы #import. Компиляция производительности тоже не проблема. Если бы это было так, мы бы не #importing Cocoa / Cocoa.h или тому подобное почти во всех заголовочных файлах.

Брюс Гудвин
источник
1
см. ответ Abizem выше для примера из документации, почему вы должны это сделать. Это защитное программирование, когда у вас есть два заголовка класса, которые импортируют друг друга с переменными экземпляра другого класса.
Jackslash
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-dependencies/ Это хорошая статья

краткое содержание статьи

импорт в заголовочных файлах:

  • # импортируйте суперкласс, который вы наследуете, и протоколы, которые вы реализуете.
  • Форвард-объявить все остальное (если это не от фреймворка с главным заголовком).
  • Попробуйте устранить все другие #imports.
  • Объявите протоколы в своих заголовках, чтобы уменьшить зависимости.
  • Слишком много предварительных деклараций? У вас большой класс.

импорт в файлах реализации:

  • Устранить ненужные # import, которые не используются.
  • Если метод делегирует другому объекту и возвращает то, что он возвращает, попробуйте объявить этот объект вперед вместо #importing.
  • Если включение модуля заставляет вас включать уровень за уровнем последовательных зависимостей, у вас может быть набор классов, который хочет стать библиотекой. Создайте его как отдельную библиотеку с главным заголовком, чтобы все можно было представить как один готовый блок.
  • Слишком много # импортов? У вас большой класс.
Гемам
источник
3

Когда я развиваюсь, я имею в виду только три вещи, которые никогда не вызывают у меня никаких проблем.

  1. Импорт супер классов
  2. Импорт родительских классов (если у вас есть дети и родители)
  3. Импортировать классы вне вашего проекта (как в фреймворках и библиотеках)

Для всех других классов (подклассы и дочерние классы в моем проекте self) я объявляю их через forward-class.

Константино Царухас
источник
3

Если вы попытаетесь объявить переменную или свойство в вашем заголовочном файле, который вы еще не импортировали, вы получите ошибку, сообщающую, что компилятор не знает этот класс.

Ваша первая мысль, вероятно, #importэто.
Это может вызвать проблемы в некоторых случаях.

Например, если вы реализуете группу C-методов в заголовочном файле, или структурах, или что-то подобное, потому что они не должны импортироваться несколько раз.

Поэтому вы можете сказать компилятору @class:

Я знаю, что вы не знаете этот класс, но он существует. Он будет импортирован или реализован в другом месте.

Он в основном говорит компилятору закрыть и скомпилировать, хотя он не уверен, будет ли когда-либо реализован этот класс.

Обычно вы будете использовать #importв .m и @classв .h файлах.

IluTov
источник
0

Переслать объявление только для того, чтобы компилятор не показывал ошибку.

компилятор будет знать, что есть класс с именем, которое вы использовали в вашем заголовочном файле для объявления.

karthick
источник
Не могли бы вы быть более конкретным?
Сэм Спенсер
0

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

Пример:

  1. Это может быть как если вы собираетесь извлечь свой класс из него или
  2. Если вы собираетесь использовать объект этого класса в качестве переменной-члена (хотя и редко).

Он не будет жаловаться, если вы просто собираетесь использовать его в качестве указателя. Конечно, вам придется #import его в файле реализации (если вы создаете экземпляр объекта этого класса), так как он должен знать содержимое класса для создания экземпляра объекта.

ПРИМЕЧАНИЕ: #import не совпадает с #include. Это означает, что нет ничего, что называется круговым импортом. import - это своего рода запрос компилятора на поиск определенного файла для получения определенной информации. Если эта информация уже доступна, компилятор игнорирует ее.

Просто попробуйте это, импортируйте Ah в Bh и Bh в Ah. Там не будет никаких проблем или жалоб, и это будет работать нормально.

Когда использовать @class

Вы используете @class, только если вы даже не хотите импортировать заголовок в свой заголовок. Это может быть случай, когда вам даже не важно знать, что это будет за класс. Случаи, когда у вас может даже не быть заголовка для этого класса.

Примером этого может быть то, что вы пишете две библиотеки. Один класс, назовем его A, существует в одной библиотеке. Эта библиотека содержит заголовок из второй библиотеки. Этот заголовок может иметь указатель A, но, опять же, может не потребоваться его использование. Если библиотека 1 еще не доступна, библиотека B не будет заблокирована, если вы используете @class. Но если вы хотите импортировать Ah, то прогресс библиотеки 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
Sujananth
источник