@class vs. #import

时间:2008-11-27 00:20:17

标签: objective-c cocoa cocoa-touch

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

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

  

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

真的很想理解这一点,而不仅仅是移除@class前向声明并抛出#import来沉默编译器给我的警告。

16 个答案:

答案 0 :(得分:747)

如果您看到此警告:

  

警告:接收者'MyCoolClass'是一个前向类,相应的@interface可能不存在

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

@class(通常)不会删除对#import文件的需求,它只是将需求移到更接近信息有用的位置。

例如

如果你说@class MyCoolClass,编译器就会知道它可能会看到:

MyCoolClass *myObject;

除了MyCoolClass是一个有效的类之外,它不必担心,它应该为指向它的指针保留空间(实际上,只是一个指针)。因此,在标题中,@class在90%的时间内都足够了。

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

答案 1 :(得分:182)

三个简单的规则:

  • 头文件(#import文件)中只有.h超类和采用的协议。
  • #import您在实施中发送消息的所有类和协议(.m文件)。
  • 其他一切的前瞻声明。

如果你在实现文件中转发声明,那么你可能做错了。

答案 2 :(得分:110)

查看ADC

上的Objective-C编程语言文档

在“定义类”一节中类接口描述了为什么这样做:

  

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

我希望这会有所帮助。

答案 3 :(得分:47)

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

异常就是你应该#import你在头文件中继承的类或正式协议(在这种情况下你不需要导入它)实施)。

答案 4 :(得分:24)

通常的做法是在头文件中使用@class(但仍需要#import超类),并在实现文件中使用#import。这将避免任何圆形内含物,它只是起作用。

答案 5 :(得分:24)

另一个优点:快速编译

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

答案 6 :(得分:18)

  

我的询问是这样的。什么时候使用#import,什么时候使用@class?

简单回答:当存在身体依赖时,您#import#include。否则,您使用前向声明(@class MONClassstruct MONStruct@protocol MONProtocol)。

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

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

有时如果我使用@class声明,我会看到一个常见的编译器警告,如下所示:      “警告:接收者'FooController'是一个前向类,相应的@interface可能不存在。”

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

您赞成前向声明的原因是您可以通过因素减少构建时间,因为依赖性最小。使用前向声明,编译器会看到有一个名称,并且可以正确地解析和编译程序,而不会在没有物理依赖性时看到类声明或其所有依赖项。清洁构建花费的时间更少。增量构建花费的时间更少。当然,您最终会花费更多的时间来确保所需的所有标题都可以在每个翻译中看到,但这样可以快速缩短构建时间(假设您的项目不是很小)。

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

对于基于C的语言而言,ObjC非常接近理想,因为NSObject类型永远不是值 - NSObject类型总是引用计数指针。因此,如果您可以适当地构建程序的依赖关系并在可能的情况下转发,那么您可以获得极快的编译时间,因为所需的物理依赖性非常小。您还可以在类扩展中声明属性,以进一步减少依赖性。这对于大型系统来说是一个巨大的好处 - 如果您开发了大型C ++代码库,您就会知道它所带来的差异。

因此,我建议尽可能使用前进,然后使用身体依赖的#import。如果您看到警告或其他暗示身体依赖的警告 - 将其全部修复。修复程序是您的实现文件中的#import

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

答案 7 :(得分:11)

我看到很多“按照这种方式行事”,但我没有看到“为什么?”的答案。

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

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

答案 8 :(得分: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(创建一个对象并使用它的函数和变量)。 / p>

答案 9 :(得分:5)

有关文件依赖关系的更多信息&amp; #import&amp; @class检查出来:

http://qualitycoding.org/file-dependencies/ 这是一篇好文章

文章摘要

  

导入头文件:

     
      
  • #import您继承的超类,以及您正在实施的协议。
  •   
  • 转发声明其他所有内容(除非它来自框架   使用主标题)。
  •   
  • 尝试消除所有其他#imports。
  •   
  • 在自己的标头中声明协议以减少依赖关系。
  •   
  • 太多的前瞻声明?你有一个大班。
  •   
     

在实现文件中导入:

     
      
  • 消除未使用过的#imports。
  •   
  • 如果方法委托给另一个对象并返回它获得的内容   回来,尝试向前声明该对象,而不是#importing它。
  •   
  • 如果包含模块强制您包含级别后的级别   连续的依赖关系,你可能有一组想要的类   成为图书馆。将其构建为具有主服务器的单独库   标题,所以一切都可以作为一个预先构建的块进行。
  •   
  • 太多#imports?你有一个大班。
  •   

答案 10 :(得分:3)

当我发展的时候,我只有三件事情从不会给我带来任何问题。

  1. 导入超类
  2. 导入父类(当您有孩子和父母时)
  3. 导入项目之外的类(如框架和库中)
  4. 对于所有其他类(我的项目中的子类和子类),我通过forward-class声明它们。

答案 11 :(得分:3)

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

你的第一个念头可能是#import 在某些情况下,这可能会导致问题。

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

因此,您可以使用@class告诉编译器:

  

我知道你不知道那个班级,但它存在。它将在其他地方导入或实施

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

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

答案 12 :(得分:0)

仅向防止编译器显示错误转发声明。

编译器会知道有一个类,你的头文件中已经使用了要声明的名称。

答案 13 :(得分:0)

只有当你打算以编译器需要知道它的实现的方式使用该类时,编译器才会抱怨。

例如:

  1. 这可能就像你要从中派生你的课程或
  2. 如果您要将该类的对象作为成员变量(尽管很少见)。
  3. 如果您只是将它用作指针,它不会抱怨。当然,您必须在实现文件中#import它(如果您要实例化该类的对象),因为它需要知道类内容以实例化对象。

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

    试试这个,在A.h中导入A.h,在A.h中导入B.h.没有任何问题或投诉也会有效。

    何时使用@class

    如果您甚至不想在标题中导入标题,则仅使用@class。这可能是您甚至不知道该课程将会是什么的情况。你甚至可能没有该课程标题的情况。

    这方面的一个例子可能是您正在编写两个库。一个类,我们称之为A,存在于一个库中。该库包含第二个库的标题。该标头可能有一个指针A但可能不需要使用它。如果库1尚不可用,则使用@class时不会阻止库B.但是如果你想导入A.h,那么库2的进度就会被阻止。

答案 14 :(得分:0)

这是一个示例场景,我们需要@class。

考虑一下你是否希望在头文件中创建一个协议,该头文件有一个数据类型相同的参数,那么你可以使用@class。请记住,你也可以单独声明协议,这只是一个例子。

// DroneSearchField.h

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

答案 15 :(得分:0)

想想@class告诉编译器“相信我,这存在”。

将#import视为复制粘贴。

出于多种原因,您希望最大限度地减少导入的数量。没有任何研究,首先想到的是它减少了编译时间。

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