根据我的理解,如果ClassA需要包含ClassB头,则应该使用前向声明,而ClassB需要包含ClassA头以避免任何循环包含.我也明白a #import
是一个简单的,ifndef
所以包含只发生一次.
我的询问是这样的:什么时候使用#import
,什么时候使用@class
?有时如果我使用@class
声明,我会看到一个常见的编译器警告,如下所示:
warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.
真的很想理解这一点,而不是仅仅删除@class
前向声明并抛出一个#import
静音编译器给我的警告.
如果您看到此警告:
警告:接收者'MyCoolClass'是一个前向类,相应的@interface可能不存在
您需要#import
该文件,但您可以在实现文件(.m)中执行此操作,并@class
在头文件中使用声明.
@class
(通常)不会删除对#import
文件的需求,它只是将需求向下移动到更接近信息有用的位置.
例如
如果你说@class MyCoolClass
,编译器知道它可能会看到类似的东西:
MyCoolClass *myObject;
除了MyCoolClass
有效的类之外,它不必担心任何事情,它应该为指向它的指针保留空间(实际上,只是一个指针).因此,在标题中,@class
90%的时间都足够了.
但是,如果您需要创建或访问其myObject
成员,则需要让编译器知道这些方法是什么.此时(可能在您的实现文件中),您需要#import "MyCoolClass.h"
告诉编译器除了"这是一个类"之外的其他信息.
三个简单的规则:
#import
头文件(.h
文件)中只有超类和采用的协议.
#import
所有类和协议,您在实现(.m
文件)中发送消息.
其他一切的前向声明.
如果你在实现文件中转发声明,那么你可能做错了.
查看ADC上的Objective-C编程语言文档
在"定义类"一节中 类接口描述了为什么这样做:
@class指令最小化了编译器和链接器看到的代码量,因此是给出类名前向声明的最简单方法.简单,它避免了导入导入其他文件的文件时可能出现的潜在问题.例如,如果一个类声明另一个类的静态类型实例变量,并且它们的两个接口文件相互导入,则这两个类都不能正确编译.
我希望这有帮助.
如果需要,#import
在头文件中使用前向声明,以及在实现中使用的任何类的头文件.换句话说,您总是#import
在实现中使用的文件,如果需要在头文件中引用类,也可以使用前向声明.
在例外的情况是,你应该#import
你从你的头文件继承一个类或正式协议(在这种情况下,你不会需要导入它在执行).
常见的做法是在头文件中使用@class(但您仍需要#import超类),并在实现文件中使用#import.这将避免任何圆形内含物,它只是起作用.
另一个优点:快速编译
如果包含头文件,则其中的任何更改都会导致当前文件也进行编译,但如果包含类名,则不会出现这种情况@class name
.当然,您需要在源文件中包含标头
我的询问是这样的.什么时候使用#import,什么时候使用@class?
简单回答:您#import
或#include
当存在身体依赖时.否则,您使用前向声明(@class MONClass
,struct MONStruct
,@protocol MONProtocol
).
以下是身体依赖的一些常见例子:
任何C或C++值(指针或引用不是物理依赖).如果你有一个CGPoint
ivar或属性,编译器将需要查看声明CGPoint
.
你的超类.
您使用的方法.
有时如果我使用@class声明,我会看到一个常见的编译器警告,如下所示:"warning:receiver'FooController'是一个转发类,相应的@interface可能不存在."
编译器在这方面实际上非常宽松.它会删除提示(例如上面的提示),但是如果忽略它们并且没有#import
正确使用它们,则可以轻松地丢弃堆栈.虽然它应该(IMO),但编译器不会强制执行此操作.在ARC中,编译器更严格,因为它负责引用计数.当遇到您调用的未知方法时,编译器会返回默认值.假设每个返回值和参数都是id
.因此,您应该根除代码库中的每个警告,因为这应该被视为物理依赖.这类似于调用未声明的C函数.使用C,假设参数为int
.
您赞成前向声明的原因是您可以按因子减少构建时间,因为依赖性很小.使用前向声明,编译器会看到有一个名称,并且可以正确地解析和编译程序,而不会在没有物理依赖性时看到类声明或其所有依赖项.清洁构建花费的时间更少.增量构建花费的时间更少.当然,您最终会花费更多的时间确保所需的所有标题对于每个翻译都是可见的,但这会在缩短的构建时间内得到回报(假设您的项目不是很小).
如果您使用#import
或#include
替代,那么您在编译器上的工作量将超过必要的范围.您还引入了复杂的头依赖项.您可以将其比作蛮力算法.当你#import
,你拖着大量不必要的信息,这需要大量的内存,磁盘I/O和CPU来解析和编译源.
对于基于C的语言而言,ObjC非常接近理想,因为NSObject
类型永远不是值 - NSObject
类型总是引用计数指针.因此,如果您可以适当地构建程序的依赖关系并在可能的情况下转发,那么您可以获得极快的编译时间,因为所需的物理依赖性非常小.您还可以在类扩展中声明属性,以进一步减少依赖性.这对于大型系统来说是一个巨大的好处 - 如果你曾经开发过大型C++代码库,你就会知道它所带来的差异.
因此,我的建议是在可能的情况下使用前锋,然后#import
在身体依赖的地方使用.如果您看到警告或其他暗示身体依赖的警告 - 将其全部修复.修复是#import
在您的实现文件中.
在构建库时,您可能会将某些接口分类为一个组,在这种情况下,您将#import
引入物理依赖性的库(例如#import
).这可能会引入依赖性,但库维护人员通常可以根据需要为您处理物理依赖关系 - 如果他们引入了一个功能,他们可以最大限度地减少它对您的构建的影响.
我看到很多"这样做",但我没有看到"为什么?"的任何答案.
那么:你为什么要在你的标题中@class和#import只在你的实现中?你不得不一直使用@class 和 #import 来加倍你的工作.除非你使用继承.在这种情况下,您将为一个@class多次#importing.然后,如果您突然决定不再需要访问声明,则必须记住从多个不同的文件中删除.
由于#import的性质,多次导入同一文件不是问题.编译性能也不是真正的问题.如果是的话,我们几乎不会在每个头文件中#importing Cocoa/Cocoa.h等.
如果我们这样做
@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(创建一个对象并使用它的函数和变量).
有关文件依赖关系和#import&@class的额外信息,请查看:
http://qualitycoding.org/file-dependencies/ 这是一篇很好的文章
文章摘要
导入头文件:
#import你继承的超类,以及你正在实现的协议.
转发声明其他所有内容(除非它来自具有主标头的框架).
尝试消除所有其他#imports.
在自己的头中声明协议以减少依赖性.
太多的前瞻性声明?你有一个大班.
在实现文件中导入:
消除未使用的cruft #imports.
如果方法委托给另一个对象并返回它返回的内容,请尝试向前声明该对象而不是#importing它.
如果包含模块强制您包含级别的连续依赖项,则可能有一组要成为库的类.将其构建为具有主标题的单独库,因此可以将所有内容作为单个预构建块引入.
#imports太多了?你有一个大班.