根据我的理解,在ClassA需要包含ClassB标头,而ClassB需要包含ClassA标头以避免任何循环包含的情况下,应该使用前向类声明。我还理解#import是一个简单的ifndef,因此include只发生一次。

我的问题是:什么时候使用#import,什么时候使用@class?有时如果我使用@class声明,我看到一个常见的编译器警告,如下所示:

警告:接收端FooController是转发类,对应的@interface可能不存在。

我真的很想理解这一点,而不是仅仅删除@class forward-declaration并抛出一个#import来沉默编译器给我的警告。


当前回答

向前声明只是为了防止编译器显示错误。

编译器会知道你在头文件中声明的类的名称。

其他回答

编译器只有在编译器需要知道其实现的情况下才会报错。

Ex:

这可以是,如果你要从中派生你的类或者 如果你要将该类的一个对象作为成员变量(尽管很少)。

如果你只是把它用作指针,它不会抱怨。当然,您必须在实现文件中#import它(如果您正在实例化该类的对象),因为它需要知道实例化对象的类内容。

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

试试这个,在B.h中导入A.h,在A.h中导入B.h。不会有任何问题或抱怨,它也会正常工作。

何时使用@class

只有当你不想在你的头文件中导入头文件时,你才可以使用@class。在这种情况下,你甚至不关心这个类是什么。在这种情况下,您甚至可能还没有该类的头文件。

例如,您正在编写两个库。一个类,我们称之为A,存在于一个库中。该库包含来自第二个库的标头。该头文件可能有一个指针a,但同样可能不需要使用它。如果库1还不可用,如果使用@class,库B将不会被阻塞。但是如果您想导入A.h,那么库2的进程就会被阻塞。

如果你试图在头文件中声明一个变量,或者一个属性,你还没有导入,你会得到一个错误,说编译器不知道这个类。

你的第一个想法可能是导入它。 在某些情况下,这可能会导致问题。

例如,如果你在头文件中实现了一堆c方法,或者结构,或者类似的东西,因为它们不应该被多次导入。

因此你可以用@class告诉编译器:

我知道你不知道那门课,但它确实存在。它将被导入或在其他地方实现

它基本上告诉编译器关闭并编译,即使它不确定这个类是否会被实现。

通常在.m文件中使用#import,在.h文件中使用@class。

如果需要,在头文件中使用前向声明,并为在实现中使用的任何类导入头文件。换句话说,您总是#import您在实现中使用的文件,如果您需要在头文件中引用一个类,也可以使用forward声明。

例外的是,你应该在头文件中导入一个类或正式协议(在这种情况下,你不需要在实现中导入它)。

可以把@class看作是告诉编译器“相信我,这是存在的”。

可以把#import看作是复制-粘贴。

出于多种原因,您希望最小化导入的数量。在没有任何研究的情况下,首先想到的是它减少了编译时间。

注意,当从类继承时,不能简单地使用前向声明。您需要导入文件,以便您声明的类知道它是如何定义的。

如果你看到这个警告:

警告:接收端MyCoolClass是转发类,对应的@interface可能不存在

您需要#导入文件,但是您可以在实现文件(.m)中这样做,并在头文件中使用@class声明。

@class并不(通常)消除#import文件的需要,它只是把需求移到更接近信息有用的地方。

例如

如果你说@class MyCoolClass,编译器知道它可能会看到类似这样的东西:

MyCoolClass *myObject;

除了MyCoolClass是一个有效的类之外,它不需要担心其他任何事情,并且它应该为指向它的指针保留空间(实际上,只是一个指针)。因此,在头文件中,@class占据了90%的时间。

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