@class против #import

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

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

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

Было бы очень приятно понять это, вместо того, чтобы просто удалить декларацию @class forward и объявить #import, чтобы отключить предупреждения, которые компилятор мне дает.

Ответы

Ответ 1

Если вы видите это предупреждение:

предупреждение: приемник "MyCoolClass" - это класс вперед, и соответствующий @interface может не существовать

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

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

Пример

Если вы скажете @class MyCoolClass, компилятор знает, что он может видеть что-то вроде:

MyCoolClass *myObject;

Не нужно беспокоиться ни о чем другом, кроме MyCoolClass является допустимым классом, и он должен зарезервировать место для указателя на него (действительно, только указатель). Таким образом, в вашем заголовке @class хватает 90% времени.

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

Ответ 2

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

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

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

Ответ 3

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

В разделе "Определение класса | Class Interface описывает, почему это делается:

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

Вот некоторые общие примеры физической зависимости:

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

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

Компилятор действительно очень мягкий в этом отношении. В нем будут отображаться подсказки (например, выше), но вы можете легко удалять свой стек, если вы игнорируете их и не выполняете #import правильно. Хотя он должен (IMO), компилятор не применяет это. В ARC компилятор более строгий, поскольку он отвечает за подсчет ссылок. Что происходит, когда компилятор возвращается к умолчанию по умолчанию, когда он встречает неизвестный метод, который вы вызываете. Каждое возвращаемое значение и параметр считаются id. Таким образом, вы должны искоренить все предупреждения из своих кодовых баз, потому что это следует рассматривать как физическую зависимость. Это аналогично вызову функции C, которая не объявлена. С C параметры считаются int.

Причина, по которой вы предпочитаете форвардные декларации, заключается в том, что вы можете сократить время сборки по факторам, потому что существует минимальная зависимость. С помощью форвардных объявлений компилятор видит, что есть имя, и может корректно анализировать и компилировать программу, не видя объявления класса или все его зависимости, когда нет физической зависимости. Чистые сборки требуют меньше времени. Инкрементальные сборки занимают меньше времени. Конечно, вы в конечном итоге потратите немного больше времени, чтобы убедиться, что все заголовки, которые вам нужны, видимы для каждого перевода, как следствие, но это быстро окупается в сокращенном времени сборки (при условии, что ваш проект не крошечный).

Если вы используете вместо #import или #include, вы бросаете гораздо больше работы на компилятор, чем это необходимо. Вы также вводите сложные зависимости заголовков. Вы можете сравнить это с алгоритмом грубой силы. Когда вы #import, вы перетаскиваете тонны ненужной информации, которая требует большого объема памяти, ввода/вывода диска и процессора для анализа и компиляции источников.

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

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

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

Ответ 8

Я вижу много "Делаю это так", но я не вижу ответов на "Почему?"

Итак: Почему вы должны @class в своем заголовке и #import только в вашей реализации? Вы удваиваете свою работу за счет @class и #import все время. Если вы не используете наследование. В этом случае вы будете #importing несколько раз для одного @class. Затем вы должны помнить об удалении из нескольких разных файлов, если вдруг решите, что вам больше не нужен доступ к объявлению.

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

Ответ 9

если мы сделаем это

@interface Class_B : Class_A

означает, что мы наследуем Class_A в Class_B, в Class_B мы можем получить доступ ко всем переменным класса_A.

если мы делаем это

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

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

Ответ 10

для дополнительной информации о зависимостях файлов и #import и @class проверьте это:

http://qualitycoding.org/file-dependencies/ itis хорошая статья

резюме статьи

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

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

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

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

Ответ 11

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

  • Импорт суперклассов
  • Импортировать родительские классы (когда у вас есть дети и родители)
  • Импортировать классы за пределы вашего проекта (например, в инфраструктурах и библиотеках)

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

Ответ 12

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

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

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

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

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

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

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

Ответ 13

Переслать декларацию только для предотвращения компиляции компилятора.

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

Ответ 14

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

Пример:

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

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

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

Просто попробуйте это, импортируйте A.h в B.h и B.h в A.h. Не будет никаких проблем или жалоб, и это тоже будет хорошо.

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

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

Примером этого может быть то, что вы пишете две библиотеки. Один класс, позволяет называть его A, существует в одной библиотеке. Эта библиотека включает в себя заголовок из второй библиотеки. Этот заголовок может иметь указатель на A, но ему может и не понадобиться использовать его. Если библиотека 1 еще не доступна, библиотека B не будет заблокирована, если вы используете @class. Но если вы хотите импортировать A.h, то прогресс библиотеки 2 блокируется.

Ответ 15

Это пример сценария, где нам нужен @class.

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

DroneSearchField.h

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