@class vs. #import - PullRequest
       92

@class vs. #import

704 голосов
/ 27 ноября 2008

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

Ответы [ 16 ]

747 голосов
/ 27 ноября 2008

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

предупреждение: получатель MyCoolClass является классом пересылки и соответствующий @interface может не существовать

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

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

Например

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

MyCoolClass *myObject;

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

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

182 голосов
/ 29 августа 2009

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

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

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

110 голосов
/ 27 ноября 2008

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

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

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

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

47 голосов
/ 27 ноября 2008

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

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

24 голосов
/ 09 сентября 2010

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

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

24 голосов
/ 27 ноября 2008

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

18 голосов
/ 08 февраля 2012

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

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

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

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

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

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

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

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

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

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

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

11 голосов
/ 29 июня 2011

Я вижу много «Делай так», но я не вижу ответов на «Почему?»

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

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

7 голосов
/ 04 января 2012

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

@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 голосов
/ 12 августа 2013

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

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

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

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

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

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

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