@class与#import


709

据我了解,如果ClassA需要包括ClassB标头,而ClassB需要包括ClassA标头,以避免任何循环包含,则应使用前向类声明。我还了解到an #import很简单,ifndef因此include仅发生一次。

我的询问是:什么时候用一次#import,什么时候用一次@class?有时,如果使用@class声明,则会看到常见的编译器警告,例如:

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

真的很想了解这一点,而不是仅仅删除@class前向声明并抛出一个#import以使编译器向我发出的警告静音。


10
前向声明只是告诉编译器,“嘿,我知道我在声明您不认识的内容,但是当我说@MyClass时,我保证将在实现中#import它。”
JoeCortopassi 2014年

Answers:


754

如果看到此警告:

警告:接收者“ MyCoolClass”是转发类,并且相应的@interface可能不存在

您需要使用#import该文件,但可以在实现文件(.m)中执行此操作,并@class在头文件中使用声明。

@class不会(通常)消除对#import文件的需求,它只是将需求向下移到信息有用的地方。

例如

如果您说@class MyCoolClass,则编译器知道它可能会看到类似以下内容的内容:

MyCoolClass *myObject;

除了MyCoolClass有效类之外,它不必担心其他任何事情,它应该为指向它的指针(实际上只是一个指针)保留空间。因此,在标题中,@class90%的时间就足够了。

但是,如果您需要创建或访问myObject的成员,则需要让编译器知道这些方法是什么。此时(大概在您的实现文件中),您将需要#import "MyCoolClass.h"告诉编译器除“这是一个类”之外的其他信息。


5
好答案,谢谢。对于未来的参考:这也涉及情况下,你@class的东西在你的.h文件,但忘记#import它在.M,尝试在上访问的方法@class编的对象,并得到这样的警告:warning: no -X method found

24
如果.h文件包含类接口所需的数据类型或其他定义,则需要#import而不是@class。
肯·阿斯佩斯拉格

2
这里没有提到的另一个巨大优势是快速编译。请参考Venkateshwar的答案
MartinMoizard 2011年

@BenGottlieb“ myCoolClass”中的'm'不应该大写吗?如“ MyCoolClass”?
罗勒·布尔克

182

三个简单的规则:

  • #import头文件(.hfile)中只有超类和采用的协议。
  • #import所有类和协议,您都将消息发送到实施中(.m文件)。
  • 转发其他声明。

如果在实现文件中进行前向声明,则可能做错了什么。


22
在头文件中,您可能还必须#import定义类采用的协议的所有内容。
泰勒

在h接口文件或m实现文件中声明#import是否有区别?
塞缪尔G

和#import如果您使用类中的实例变量
user151019 2012年

1
@Mark-受规则1的限制,即使是,也只能从您的超类访问ivars。
PeyloW 2012年

@Tyler为什么不转发协议声明?
JoeCortopassi

110

查看ADC上的Objective-C编程语言文档

在“定义类”部分下| 类接口描述了这样做的原因:

@class指令最大程度地减少了编译器和链接器看到的代码量,因此是对类名进行前向声明的最简单方法。很简单,它避免了导入其他文件时可能带来的潜在问题。例如,如果一个类声明了另一个类的静态类型的实例变量,并且它们的两个接口文件相互导入,则任何一个类都可能无法正确编译。

我希望这有帮助。


48

如果需要,请#import在头文件中使用前向声明,并在实现中使用的任何类的头文件中使用。换句话说,您总是#import在实现中使用的文件,并且如果需要在头文件中引用类,则也要使用前向声明。

例外的情况是,你应该#import一类或正式协议,当您在你的头文件继承(在这种情况下,你不会需要导入它在执行)。


24

通常的做法是在头文件中使用@class(但您仍然需要#import超类),并在实现文件中#import。这样可以避免任何圆形夹杂物,并且可以正常工作。


2
我以为#import比#include更好,因为它只导入一个实例?
马修·辛克尔

2
真正。不知道这是关于循环包含还是不正确的排序,但是我冒险逃脱了该规则(在标头中仅导入一个,子类的实现中不再需要导入),并且很快变得非常混乱。最重要的是,遵循该规则,编译器将很高兴。
Steph Thirion,

1
当前文档说,#import“像C语言的#include指令,但是它可以确保相同的文件永远不会包含不止一次。” 因此,根据这种#import方法来处理循环包含,@class指令对此并没有特别的帮助。
艾瑞克(Eric)

24

另一个优点:快速编译

如果包含头文件,则其中的任何更改都会导致当前文件也进行编译,但是如果类名包含为,则情况并非如此@class name。当然,您需要在源文件中包含标题


18

我的询问是这个。什么时候使用#import和何时使用@class?

简单的答案:您#import#include有身体依赖性时。否则,您使用前向声明(@class MONClassstruct MONStruct@protocol MONProtocol)。

以下是一些常见的身体依赖性示例:

  • 任何C或C ++值(指针或引用不是物理依赖性)。如果您具有CGPointivar或属性,则编译器将需要查看的声明CGPoint
  • 你的超人。
  • 您使用的方法。

有时,如果我使用@class声明,则会看到常见的编译器警告,例如以下内容:“警告:接收者'FooController'是正向类,并且相应的@interface可能不存在。

编译器在这方面实际上非常宽容。它将删除提示(例如上面的提示),但是如果您忽略它们并且操作不#import正确,则可以轻松地破坏堆栈。尽管应该(IMO),但编译器不会强制执行此操作。在ARC中,编译器更加严格,因为它负责引用计数。发生的情况是,当编译器遇到您调用的未知方法时,它会使用默认值。假定每个返回值和参数均为id。因此,您应该消除代码库中的所有警告,因为这应被视为物理依赖性。这类似于调用未声明的C函数。对于C,假定参数为int

您偏爱前向声明的原因是因为可以将依赖关系降到最低,因此可以通过一些因素减少构建时间。使用前向声明,编译器可以看到一个名称,并且可以在没有物理依赖项的情况下正确解析和编译程序,而无需查看类声明或其所有依赖项。干净的构建花费更少的时间。增量构建花费的时间更少。当然,您最终将花费更多的时间来确保每个翻译都可以看到所需的所有标头,但这可以迅速减少构建时间(不算小项目)。

如果使用#import#include代替,那么您在编译器上投入的工作比必要的要多得多。您还将引入复杂的标头依赖项。您可以将其比作蛮力算法。当您使用时#import,您将拖入大量不必要的信息,这需要大量内存,磁盘I / O和CPU来解析和编译源。

在依赖方面,ObjC非常接近于基于C的语言的理想选择,因为NSObject类型从不值- NSObject类型始终是引用计数的指针。因此,如果适当地构建程序的依赖项并尽可能进行转发,则可以避免令人难以置信的快速编译时间,因为几乎不需要物理依赖项。您还可以在类扩展中声明属性,以进一步降低依赖性。对于大型系统而言,这是一个巨大的好处-如果您曾经开发过大型C ++代码库,您就会知道它的不同之处。

因此,我的建议是在可能的情况下使用转发,然后#import在存在物理依赖性的地方使用转发。如果看到警告或其他暗示着身体依赖的警告,请全部解决。该修复程序#import位于您的实现文件中。

在构建库时,您可能会将某些接口归为一组,在这种情况下,您会将#import引入物理依赖关系的库(例如#import <AppKit/AppKit.h>)。这可能会引入依赖关系,但是库维护人员通常可以根据需要为您处理物理依赖关系-如果他们引入了功能,则可以最大程度地减少对构建的影响。


顺便说一句,顺便说一句。。但是它们似乎很复杂。
Ajay Sharma

NSObject types are never values -- NSObject types are always reference counted pointers.并非完全正确。障碍只是说出了漏洞。
理查德·罗斯三世

@ RichardJ.RossIII…和GCC允许声明和使用值,而clang禁止这样做。当然,指针后面必须有一个值。
贾斯汀2012年

11

我看到很多“以这种方式执行”,但没有看到“为什么?”的任何答案。

因此:为什么您应该在标头中@class和仅在实现中#import?您必须一直使用@class #import 来使工作量加倍。除非您使用继承。在这种情况下,您将#import单个@class多次。然后,如果突然决定不再需要访问声明,则必须记住从多个不同的文件中删除。

由于#import的性质,多次导入同一文件不是问题。编译性能也不是真正的问题。如果是这样,我们几乎就不会在每个头文件中都#import Cocoa / Cocoa.h等。


1
请参阅上面的Abizem答案,以获取有关为何应执行此操作的文档中的示例。它具有防御性的编程,用于当您有两个类头,这些头与另一个类的实例变量相互导入时。
jackslash 2012年

7

如果我们这样做

@interface Class_B : Class_A

这意味着我们将继承Class_A到Class_B,在Class_B中,我们可以访问class_A的所有变量。

如果我们这样做

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

这里我们说我们在程序中使用Class_A,但是如果要在Class_B中使用Class_A变量,则必须在.m文件中#import Class_A(创建一个对象并使用它的函数和变量)。


5

有关文件依赖性&#import&@class的更多信息,请查看以下内容:

http://qualitycoding.org/file-dependencies/ 好的文章

文章摘要

导入头文件:

  • #import您要继承的超类以及要实现的协议。
  • 转发声明其他所有内容(除非它来自具有主标头的框架)。
  • 尝试消除所有其他#import。
  • 在自己的标头中声明协议以减少依赖性。
  • 太多的前向声明?你有一个大班。

导入实现文件:

  • 消除未使用的#cft导入。
  • 如果一个方法委托给另一个对象并返回它返回的内容,请尝试向前声明该对象,而不是#import。
  • 如果包含模块迫使您在逐级依赖关系中逐级包含,则您可能有一组要成为库的类。将其构建为具有主标头的独立库,因此所有内容都可以作为单个预构建块引入。
  • #import太多?你有一个大班。

3

当我发展时,我只想着三件事,它们永远不会给我带来任何问题。

  1. 导入超级类
  2. 导入家长班(如果您有孩子和父母)
  3. 在项目外部导入类(例如在框架和库中)

对于所有其他类(项目自身中的子类和子类),我通过正向类声明它们。


3

如果您尝试在头文件中声明尚未导入的变量或属性,则会收到一条错误消息,指出编译器不知道此类。

您的第一个想法可能就是#import它。
在某些情况下,这可能会引起问题。

例如,如果您在头文件,结构或类似文件中实现了一堆C方法,因为它们不应多次导入。

因此,您可以通过以下方式告诉编译器@class

我知道您不知道该课程,但是它存在。它将被导入或在其他地方实现

它基本上告诉编译器关闭并编译,即使不确定是否要实现此类也是如此。

你通常会使用#import.M@class.H文件。


0

仅将声明转发给阻止编译器显示错误。

编译器将知道存在一个类,该类具有您在头文件中用于声明的名称。


您能具体一点吗?
山姆·斯宾塞

0

仅当您打算以使编译器需要了解其实现的方式使用该类时,编译器才会抱怨。

例如:

  1. 这就像如果您要从中派生您的课程,或者
  2. 如果您打算将该类的对象作为成员变量(虽然很少见)。

如果您只是将其用作指针,它不会抱怨。当然,您必须将其导入实现文件中(如果要实例化该类的对象),因为它需要知道类内容才能实例化一个对象。

注意:#import与#include不同。这意味着没有什么叫做循环导入。导入是一种要求编译器查看特定文件以获取某些信息的请求。如果该信息已经可用,则编译器将忽略它。

只需尝试一下,将Bh中的Ah导入Ah,Bh中的Bh导入就不会有任何问题或投诉,并且也可以正常工作。

何时使用@class

仅当您甚至不想在标头中导入标头时,才使用@class。在这种情况下,您甚至都不希望知道该班级是什么。甚至没有该类标题的情况。

例如,您正在编写两个库。一个类(称为A)存在于一个库中。该库包括第二个库的头。该标头可能具有A指针,但再次可能不需要使用它。如果库1尚不可用,则使用@class时将不会阻止库B。但是,如果您要导入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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.