@class和#import有什么区别?

9
当我使用以下代码进行编译时,没有出现错误:
@class RootViewController;
//#import "RootViewController.h"

当我使用以下代码编译时,出现错误:
//@class RootViewController;
#import "RootViewController.h"

"错误:在 'RootViewController' 之前需要指定限定符列表"

我不明白这两者之间的区别,因为我在一个类中使用了 #import ,并且它编译时没有出现错误!


1
在您的情况下,您的RootViewController.h文件或其导入的文件中存在问题。可能是某个地方缺少了“;”。不幸的是,调试头文件有些困难,因为您经常会在另一个文件中收到错误信息。使用@class时不会出错的原因是它不会加载包含错误的文件。请参阅我的答案以获取更多详细信息。 - Felixyz
可能是Objective-C @class vs. #import的重复问题。 - willcodejavaforfood
8个回答

32

@class用于在特定文件中需要知道类的名称,但不需要知道关于这个类的任何详细信息(例如其方法)。#import用于实际需要使用该类(即发送消息)的情况。

例如,如果您在头文件中声明实例变量,可以使用@class来声明某种类型的实例变量:

@class MyOtherClass;

@interface MyClass : NSObject
{
    MyOtherClass *myIvar;
}
@end

因为你还没有使用myIvar,所以除了需要知道类型MyOtherClass存在外,你不需要知道关于它的任何信息。

但是:

#import "MyOtherClass.h"

- (void)doSomething
{
    [myIvar doSomethingElse];
}
在这种情况下,您正在将 doSomethingElse 消息发送给 myIvar;编译器需要知道 MyOtherClass 的实例定义了此方法,因此您必须导入头文件,否则编译器会抱怨。
为什么要担心这个问题?
主要与依赖项有关。当您将文件A#import到文件B中时,文件B就变成了对文件A的依赖 - 也就是说,如果文件A发生更改,您必须重新编译文件B。如果您在文件B中使用@class,则文件B不依赖于文件A,因此当文件A更改时无需重新编译文件B - 因此,如果您仅声明类型而不实际依赖于文件A的实现,则可以通过不#import文件A来节省编译时间。

8
我决定查阅文档,因为我仍然感到困惑: #import 该指令与#include相同,但确保同一文件不会被多次包含。因此,它是首选的,并在基于Objective-C的文档中用于代码示例。
这个约定意味着每个接口文件都包含所有继承类的接口文件。当源模块导入类接口时,它会获得类所构建的整个继承层次结构的接口。 @class 这样的声明只使用类名作为类型,不依赖于类接口的任何细节(其方法和实例变量),@class指令足以让编译器知道将要发生什么。但是,在实际使用类的接口(创建实例,发送消息)时,必须导入类接口。 http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocDefiningClasses.html#//apple_ref/doc/uid/TP30001163-CH12-TPXREF123

顺便说一句,如果你想知道为什么我还没有接受答案 - 那是因为这个系统在两天内不允许我接受答案 - 不知道为什么需要这样做。 - TheLearner

7
基本规则:在头文件中使用@class,在实现文件中使用#import。(但是,您需要#import类的超类。在其他一些情况下,您还需要在头文件中使用#import。) #import#include不等效。如果一个文件被#include多次,它将每次都被加载,但是对于同一文件的多个#import,它仍然只会被加载一次。
因此,使用@class的主要原因不是为了避免循环依赖,而是为了加快编译速度。
以下是必须使用@class的示例。
//MYControl.h

@class MYControl;  // Must use class

@protocol MYControlDelegate
-(void)control:(MYControl *)control didChangeToState:(UIControlState)state;
@end

@interface MYControl : UIControl
{
   id<MYControlDelegate> delegate_;
}
@property (nonatomic, assign) id<MYControlDelegate> delegate;
@end

//MYControl.m

@implementation MYControl
@synthesize delegate = delegate_;
. . .

在这种情况下,没有什么需要导入的,因为委托协议在头文件中的主类之上已经声明了。但是你仍然需要能够引用尚未声明的主类。所以,@class 所做的就是让编译器知道有一个名为 MYControl 的类,并且在某些时候它将被定义。(但是不是在运行时,该类将在编译过程中定义。)
编辑:从 Objective-C 手册:
由于这样的声明只使用类名作为类型,而不依赖于类接口的任何细节(方法和实例变量),所以 @class 指令给编译器提供了足够的预警。但是,在实际使用类的接口(创建实例、发送消息)时,必须导入类接口。通常,接口文件使用 @class 声明类,相应的实现文件导入它们的接口(因为它将需要创建那些类的实例或发送消息给它们)。
@class 指令最小化了编译器和链接器所看到的代码量,因此是给出类名称的前向声明的最简单方法。它是简单的,可以避免导入其他文件的潜在问题。例如,如果一个类声明另一个类的静态类型实例变量,并且它们的两个接口文件相互导入,那么两个类都可能无法正确编译。
请注意,循环性是作为使用 @class 处理的一般问题之一在最后一句中提到的。

为什么你说 @class 不是为了避免循环依赖,然后又提供一个正好使用它来避免循环依赖的示例呢?在你的示例中,MyControl 类依赖于 MyControlDelegate 协议,而 MyControlDelegate 又依赖于 MyControl - JeremyP
顺便提一下,您的变量声明和委托类型的属性是错误的。它们应该像这样:id<MyControlDelegate> delegate_; - JeremyP
@JeremyP:关于ivar/property声明:谢谢,我已经改正了。关于循环引用问题,我说了,循环引用并不是@class存在的主要原因。@class的目的是为了减少任何类型的引用以加速编译。循环引用也可以用其他方式处理,虽然@class在这方面也很方便。我认为这些陈述反映在我添加到答案中的引语中。说到这一点,没有什么阻止我展示一个例子,在其中@class有助于处理一个文件内的循环引用。 - Felixyz
我认为你是错的。编译速度从来不是使用 @class 的原因。也许你可以提供一些引用资料。 - JeremyP
1
编译速度是我听到使用@class最大原因。 - mipadi

6

@class用于避免循环依赖......这可以防止循环引用,其中一个头文件A导入了第二个头文件B,该文件B又导入了第一个头文件A,然后再无限循环下去......@class通常用于要求编译器在运行时查找其定义......特别是当它驻留在某个静态库中时。

除此之外,#import也可以正常工作。

参见这个问题


3
我不同意 "@class" 主要用于避免循环引用的说法,因为 "#import" 已经解决了这个问题。(试一下吧!) - Felixyz
接受不同意见... @class 通常用于请求编译器在运行时查找其定义... 特别是当它驻留在某些静态库中时... - Mihir Mehta
1
@Felixyz:抱歉,但您是错误的。@class 的主要用途是避免 @interface 和 @protocol 定义之间的循环依赖关系。 - JeremyP
@JeremyP:我之前说“#import已经处理了这个问题”是错误的,因为它并不总是有效。然而,我认为说@protocol定义是@class的“主要用途”有些夸张了。例如,在我回答中添加的引用中,并没有提到@protocol。 - Felixyz
@Felixyz:我不是那个意思。我的意思是使用@class是为了避免@interface之间的循环依赖,同时也用于避免@protocol之间的循环依赖,以及两者的组合。 - JeremyP
这是它的主要用途,我一直在使用 #import 并忽略 @class,直到我遇到循环导入的问题。 - Ramy Al Zuhouri

2

0

@class是一个前向声明,一个好的实践是将它们放在.h文件中而不是使用#import,以避免循环导入问题。


0

你必须在想要导入的类中导入这个类。这就是为什么你会得到错误,但可以通过@class example进行矫正。


0

@class 表示类 RootViewController 的定义尚未声明,但将在运行时定义。我认为它类似于在 C++ 中声明外部类。

#import 相当于 #include。

通过错误信息,我可以猜测你在 RootViewController.h 中犯了一个错误,比如忘记了 ; 或者类似的东西。


1
说“它将在运行时定义”是不正确的。该类肯定会在编译过程中的某个时刻被定义。实际上,它可能已经被定义了。@class只是表示在这个文件的上下文中,我们不会访问该类的任何字段或方法,因此我们只需要知道它的名称,以便我们可以声明指向它的指针。 - Felixyz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接