@class与#import

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

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

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

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

如果你看到这个警告:

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

你需要#import这个文件,但是你可以在你的实现文件(.m)中做这个,并且在头文件中使用@class声明。

@class不会(通常)删除#import文件的需要,它只是将需求更靠近信息有用的地方。

例如

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

 MyCoolClass *myObject; 

除了MyCoolClass是一个有效的类以外,不用担心任何事情,它应该为指向它的指针保留空间(实际上,只是一个指针)。 因此,在你的头文件中, @class足够了90%的时间。

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

三个简单的规则:

  • 只有#import头文件( .h文件)中导入超类,并采用了协议。
  • #import所有类和协议,将消息发送到实现( .m文件)。
  • 前进宣言的一切。

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

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

在定义类的部分下 类接口它描述了为什么这样做:

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

我希望这有帮助。

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

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

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

另一个好处:快速编译

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

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

简单的回答:你有#import#include当有物理依赖。 否则,你使用正向声明( @class MONClassstruct MONStruct@protocol MONProtocol )。

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

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

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

编译器在这方面实际上非常宽松。 它会放弃提示(比如上面的提示),但是如果你忽略它们,并且不能正确地input,你可以很容易地把你的堆栈清理掉。 虽然它应该(IMO),编译器不强制执行此操作。 在ARC中,编译器更为严格,因为它负责引用计数。 当遇到你所称的未知方法时,编译器会回到默认状态。 每个返回值和参数都假定为id 。 因此,你应该消除你的代码库的每一个警告,因为这应该被视为物理依赖。 这类似于调用未声明的C函数。 使用C,参数被假定为int

你喜欢前向声明的原因是你可以减less你的构build时间的因素,因为有最小的依赖。 使用前向声明,编译器会看到有一个名称,并且可以正确地parsing和编译程序,而不会在没有物理依赖关系的情况下查看类声明或所有依赖项。 清理构build需要更less的时间。 增量构build需要更less的时间。 当然,最终你会花费更多的时间来确保每一个翻译都能看到你需要的所有头文件,但是这样可以快速缩短构build时间(假设你的项目不是很小)。

如果使用#import#include ,则在编译器上投入了大量的工作。 您还引入了复杂的标题依赖关系。 你可以把这个比喻成一个蛮力algorithm。 当你#import ,你正在拖拽大量不必要的信息,这需要大量的内存,磁盘I / O和CPU来parsing和编译源代码。

ObjC对于一个基于C的语言来说是相当接近理想的,因为NSObjecttypes永远不会是值 – NSObjecttypes总是引用计数的指针。 所以,如果你在适当的时候合理地构造你的程序的依赖关系,那么你可以很快地完成编译,因为只需要很less的物理依赖。 您还可以在类扩展中声明属性以进一步减less依赖性。 对于大型系统来说,这是一个巨大的好处 – 如果您曾经开发过大型的C ++代码库,那么您将会知道它的不同之处。

因此,我的build议是尽可能使用前锋,然后#import进入有身体依赖的地方。 如果您看到警告或其他暗示身体依赖的警告 – 将其全部修复。 解决的办法是#import你的实现文件。

在构build库时,您可能会将一些接口归类为一个组,在这种情况下,您将#import该库引入物理依赖关系(例如#import <AppKit/AppKit.h> )。 这可能会引入依赖性,但是图书馆维护人员通常可以根据需要为您处理物理依赖关系 – 如果他们引入了某个function,则可以将其对构build的影响降至最低。

我看到很多“这样做”,但我没有看到“为什么”的答案。

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

由于#import的性质,多次导入相同的文件不是问题。 编译性能也不是真正的问题。 如果是这样的话,我们就不会在几乎所有的头文件中#importing Cocoa / Cocoa.h或类似的东西。

如果我们这样做

 @interface Class_B : Class_A 

意思是我们inheritanceClass_A到Class_B,在Class_B我们可以访问class_A的所有variables。

如果我们正在这样做

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

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

有关文件依赖关系的更多信息&#import&@class check this out:

http://qualitycoding.org/file-dependencies/这是一个很好的文章;

文章摘要

在头文件中导入:

  • #导入你正在inheritance的超类,以及你正在实现的协议。
  • 向前声明一切(除非它来自具有主标题的框架)。
  • 尝试消除所有其他#imports。
  • 在自己的头文件中声明协议以减less依赖性。
  • 太多的前瞻性声明? 你有一个大class。

在执行文件中导入:

  • 消除未使用的cruft#import。
  • 如果一个方法委托给另一个对象,并返回它返回的东西,试着转发 – 声明该对象而不是#importing它。
  • 如果包含一个模块会迫使你在连续的依赖关系级别之后包含关卡,那么你可能有一组想要成为库的类。 把它作为一个带有主标题的单独的库来构build,所以所有的东西都可以作为一个单独的预build的块来引入。
  • 太多#imports? 你有一个大class。

当我发展的时候,我只有三件事情,不会造成任何问题。

  1. 导入超类
  2. 导入父母课程(当你有孩子和父母)
  3. 在项目之外导入类(如在框架和库中)

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

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

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

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

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

我知道你不知道那个class,但它是存在的。 它将在其他地方导入或执行

它基本上告诉编译器闭嘴和编译,即使不知道这个类是否将被实现。

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

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

编译器会知道你的头文件中声明的类有你用过的名字。

只有编译器需要知道它的实现时,才会使用该类。

例如:

  1. 这可能是,如果你要从它或派生你的课
  2. 如果你将这个类的对象作为成员variables(虽然很less见)。

如果你只是将它用作指针,它不会抱怨。 当然,你必须在实现文件中input它(如果你正在实例化这个类的一个对象),因为它需要知道类内容来实例化一个对象。

注:#import与#include不一样。 这意味着没有什么叫循环导入。 导入是一种请求编译器查看特定文件的一些信息。 如果这些信息已经存在,编译器会忽略它。

只要试试这个,在Bh中inputAh,在Ah中inputBh就没有问题或者抱怨,它也可以正常工作。

何时使用@class

只有在你甚至不想在头文件中导入头文件时才使用@class。 这可能是你甚至不关心这个class会是什么样子的情况。 你甚至可能还没有这个类的头文件。

一个例子可能是你正在写两个库。 一个类叫A,存在于一个库中。 这个库包含第二个库的头文件。 这个头可能有一个A的指针,但可能不需要使用它。 如果库1还不可用,那么如果使用@class,库B将不会被阻塞。 但是如果你想要导入啊,那么库2的进度就被阻塞了。

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

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

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