@class与#import
据我了解,如果ClassA需要包括ClassB标头,而ClassB需要包括ClassA标头,以避免任何循环包含,则应使用前向类声明。 我也理解#import
是一个简单的ifndef
因此一个include仅发生一次。
我的查询是:什么时候使用#import
和何时使用@class
? 有时,如果我使用@class
声明, @class
看到常见的编译器警告,例如:
warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.
真的很想了解这一点,而不是仅仅删除@class
前向声明,然后抛出#import
来使编译器向我发出的警告静音。
#1楼
仅将声明转发给阻止编译器显示错误。
编译器将知道存在一个类,该类具有您在头文件中用于声明的名称。
#2楼
三个简单的规则:
- 仅
#import
将超类和采用的协议导入头文件(.h
文件)中。 -
#import
所有类和协议,您可以在实施中发送消息(.m
文件)。 - 转发其他声明。
如果在实现文件中进行前向声明,则可能做错了什么。
#3楼
如果您尝试在头文件中声明尚未导入的变量或属性,则会收到一条错误消息,指出编译器不知道此类。
您的第一个想法可能是#import
它。
在某些情况下,这可能会引起问题。
例如,如果您在头文件,结构或类似文件中实现了一堆C方法,因为它们不应多次导入。
因此,您可以使用@class
告诉编译器:
我知道您不知道该课程,但是它存在。 它将被导入或在其他地方实现
它基本上告诉编译器关闭并编译,即使不确定是否要实现此类也是如此。
通常,您将在.m文件中使用#import
并在.h文件中使用@class
。
#4楼
仅当您打算以使编译器需要了解其实现的方式使用该类时,编译器才会抱怨。
例如:
- 这就像如果您要从中派生您的课程,或者
- 如果您打算将该类的对象作为成员变量(虽然很少见)。
如果您只是将其用作指针,它不会抱怨。 当然,您必须将其导入实现文件中(如果要实例化该类的对象),因为它需要知道类内容才能实例化一个对象。
注意:#import与#include不同。 这意味着没有什么叫做循环导入。 导入是一种要求编译器查看特定文件以获取某些信息的请求。 如果该信息已经可用,则编译器将忽略它。
只需尝试一下,将Bh中的Ah导入Ah,Bh中的Bh导入就不会有问题或投诉,并且也可以正常工作。
何时使用@class
仅当您甚至不想在标头中导入标头时,才使用@class。 在这种情况下,您甚至都不希望知道该班级是什么。 甚至没有该类标题的情况。
例如,您正在编写两个库。 一个类(称为A)存在于一个库中。 该库包括第二个库的头。 该标头可能具有A指针,但再次可能不需要使用它。 如果库1尚不可用,则使用@class时将不会阻止库B。 但是,如果您要导入Ah,则库2的进度将被阻止。
#5楼
有关文件依赖性&#import&@class的更多信息,请查看以下内容:
http://qualitycoding.org/file-dependencies/好的文章
文章摘要
导入头文件:
- #import您要继承的超类以及要实现的协议。
- 转发声明其他所有内容(除非它来自具有主标头的框架)。
- 尝试消除所有其他#import。
- 在自己的标头中声明协议以减少依赖性。
- 太多的前向声明? 你有一个大班。
导入实现文件:
- 消除未使用的#cft导入。
- 如果一个方法委托给另一个对象并返回它返回的内容,请尝试向前声明该对象,而不是#import。
- 如果包含模块迫使您在逐级依赖关系中逐级包含,则您可能有一组要成为库的类。 将其构建为具有主标头的独立库,因此所有内容都可以作为单个预构建块引入。
- #import太多? 你有一个大班。
#6楼
如果需要,请在头文件中使用前向声明,并#import
您在实现中使用的任何类的头文件。 换句话说,您总是#import
导入您在实现中使用的文件,并且,如果您需要在头文件中引用一个类,也请使用前向声明。
例外的情况是,你应该#import
你从你的头文件继承一个类或正式协议(在这种情况下,你不会需要导入它在执行)。
#7楼
如果看到此警告:
警告:接收器“ MyCoolClass”是转发类,并且相应的@interface可能不存在
您需要#import
文件,但是可以在实现文件(.m)中执行此操作,并在头文件中使用@class
声明。
@class
并不会(通常)消除对#import
文件的需求,它只是将需求向下移到信息有用的地方。
例如
如果您说@class MyCoolClass
,则编译器知道它可能会显示类似以下内容:
MyCoolClass *myObject;
除了MyCoolClass
是有效的类之外,它不必担心其他任何事情,它应该为指向它的指针(实际上只是一个指针)保留空间。 因此,在标题中, @class
足以满足90%的时间要求。
但是,如果您需要创建或访问myObject
的成员,则需要让编译器知道这些方法是什么。 此时(大概在您的实现文件中),您将需要#import "MyCoolClass.h"
,以告诉编译器除“这是一个类”之外的其他信息。
#8楼
通常的做法是在头文件中使用@class(但您仍然需要#import超类),并在实现文件中#import。 这样可以避免任何圆形夹杂物,并且可以正常工作。
#9楼
查看ADC上的Objective-C编程语言文档
在“定义类”部分下| 类接口描述了这样做的原因:
@class指令最大程度地减少了编译器和链接器看到的代码量,因此是对类名进行前向声明的最简单方法。 很简单,它避免了导入其他文件时可能带来的潜在问题。 例如,如果一个类声明了另一个类的静态类型的实例变量,并且它们的两个接口文件相互导入,则这两个类都可能无法正确编译。
我希望这有帮助。
#10楼
另一个优点:快速编译
如果包含头文件,则其中的任何更改都会导致当前文件也进行编译,但是如果将类名作为@class name
包含在内,则情况并非如此。 当然,您需要在源文件中包含标题
#11楼
这是一个示例场景,我们需要@class。
考虑是否要在头文件中创建一个协议,该协议的参数具有相同类的数据类型,那么可以使用@class。 请记住,您也可以单独声明协议,这只是一个示例。
// DroneSearchField.h
#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
#12楼
将@class视为告诉编译器“相信我,这个存在”。
将#import视为复制粘贴。
由于多种原因,您想使进口数量最少。 没有任何研究,想到的第一件事就是它减少了编译时间。
请注意,从类继承时,不能简单地使用前向声明。 您需要导入文件,以便您声明的类知道其定义方式。
#13楼
当我发展时,我只想着三件事,它们永远不会给我带来任何问题。
- 导入超级类
- 导入家长班(如果您有孩子和父母)
- 在项目外部导入类(例如在框架和库中)
对于所有其他类(项目自身中的子类和子类),我通过正向类声明它们。
#14楼
我看到很多“以这种方式执行”,但没有看到“为什么?”的任何答案。
因此: 为什么您应该在标头中@class和仅在实现中#import? 您必须一直使用@class 和 #import来使工作量加倍。 除非您使用继承。 在这种情况下,您将#import单个@class多次。 然后,如果突然决定不再需要访问声明,则必须记住从多个不同的文件中删除。
由于#import的性质,多次导入同一文件不是问题。 编译性能也不是真正的问题。 如果是这样,我们几乎就不会在每个头文件中都#import Cocoa / Cocoa.h等。
#15楼
如果我们这样做
@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(创建一个对象并使用它的函数和变量)。
#16楼
我的询问是这个。 什么时候使用#import和何时使用@class?
简单的答案:如果存在物理依赖性,则可以#import
或#include
。 否则,您将使用前向声明( @class MONClass
, struct MONStruct
, @protocol MONProtocol
)。
以下是一些常见的身体依赖性示例:
- 任何C或C ++值(指针或引用不是物理依赖性)。 如果您将
CGPoint
作为ivar或属性,则编译器将需要查看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>
)。 这可能会引入依赖关系,但是库维护人员通常可以根据需要为您处理物理依赖关系-如果他们引入了功能,则可以最大程度地减少对构建的影响。