首页 文章

@class vs. #import

提问于
浏览
700

根据我的理解,如果ClassA需要包含ClassB头,则应该使用前向声明,而ClassB需要包含ClassA头以避免任何循环包含 . 我也明白 #import 是一个简单的 ifndef ,所以包含只发生一次 .

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

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

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

16 回答

  • 0

    如果您看到此警告:

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

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

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

    For Example

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

    MyCoolClass *myObject;
    

    除了 MyCoolClass 是一个有效的类之外,它不必担心任何事情,它应该为指向它的指针保留空间(实际上,只是一个指针) . 因此,在您的 Headers 中, @class 足以满足90%的时间 .

    但是,如果您需要创建或访问 myObject 's members, you' ll需要让编译器知道这些方法是什么 . 此时(可能在您的实现文件中),您需要 #import "MyCoolClass.h" ,告诉编译器除了"this is a class"之外的其他信息 .

  • 3

    三个简单的规则:

    • 只有 #import 超级类,并采用了头文件中的协议( .h 文件) .

    • #import 所有类和协议,您在实现中发送消息( .m 文件) .

    • 其他一切的前向声明 .

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

  • 0

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

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

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

    我希望这有帮助 .

  • 0

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

    exception 这是你应该 #import 你需要在实现中导入它的类或正式协议) .

  • 18

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

  • 24

    另一个优点:快速编译

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

  • 11

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

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

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

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

    • 你的超类 .

    • 您使用的方法 .

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

    编译器's actually very lenient in this regard. It will drop hints (such as the one above), but you can trash your stack easily if you ignore them and don' t #import 正常 . 虽然它应该(IMO),但编译器不会强制执行此操作 . 在ARC中,编译器更严格,因为它负责引用计数 . 当遇到您调用的未知方法时,编译器会返回默认值 . 假设每个返回值和参数都是 id . 因此,您应该根除代码库中的每个警告,因为这应该被视为物理依赖 . 这类似于调用未声明的C函数 . 使用C,假设参数为 int .

    您赞成前向声明的原因是您可以按因子减少构建时间,因为依赖性很小 . 使用前向声明,编译器会看到有一个名称,并且可以正确地解析和编译程序,而不会在没有物理依赖性时看到类声明或其所有依赖项 . 清洁构建花费的时间更少 . 增量构建花费的时间更少 . 当然,您最终会花费更多的时间确保所需的所有 Headers 对于每个翻译都是可见的,但这会在缩短的构建时间内得到回报(假设您的项目不是很小) .

    如果您使用 #import#include ,则're throwing a lot more work at the compiler than is necessary. You'也会引入复杂的标头依赖项 . 您可以将其比作蛮力算法 . 当你 #import 时,你会拖入大量不必要的信息,这需要大量的内存,磁盘I / O和CPU来解析和编译源代码 .

    对于依赖于C的语言而言,ObjC非常接近理想,因为 NSObject 类型永远不会是值 - NSObject 类型总是引用计数指针 . 因此,如果您构建程序's dependencies appropriately and forward where possible because there is very little physical dependence required. You can also declare properties in the class extensions to further minimize dependence. That'对于大型系统来说是一个巨大的奖励,那么您可以获得令人难以置信的快速编译时间 - 如果您开发了大型C代码库,您就会知道它所带来的差异 .

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

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

  • 24

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

    那么:你为什么要在你的 Headers 中@class和#import只在你的实现中?你不得不一直使用@class和#import来加倍你的工作 . 除非你使用继承 . 在这种情况下,您需要再次访问声明 .

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

  • 181

    如果我们这样做

    @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(创建一个对象并使用它的函数和变量) .

  • 47

    有关文件依赖关系和#import&@class的额外信息,请查看:

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

    文章摘要

    头文件中的导入:#import您继承的超类,以及您正在实现的协议 . 转发声明其他所有内容(除非它来自具有主标头的框架) . 尝试消除所有其他#imports . 在自己的头中声明协议以减少依赖性 . 太多的前瞻性声明?你有一个大班 . 在实现文件中导入:消除未使用的cruft #imports . 如果方法委托给另一个对象并返回它返回的内容,请尝试向前声明该对象而不是#importing它 . 如果包含模块强制您包含级别的连续依赖项,则可能有一组要成为库的类 . 将其构建为具有主 Headers 的单独库,因此可以将所有内容作为单个预构建块引入 . #imports太多了?你有一个大班 .

  • 742

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

    • 导入超类

    • 导入父类(当你有孩子和父母时)

    • 导入项目之外的类(如框架和库中)

    对于所有其他类(我的项目中的子类和子类),我通过forward-class声明它们 .

  • 110

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

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

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

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

    我知道你不知道那个 class ,但它确实存在 . 它将在其他地方导入或实施

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

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

  • 3

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

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

  • 0

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

    例如:

    • 这可能就像你要从中派生你的课程一样

    • 如果您要将该类的对象作为成员变量(尽管很少见) .

    如果您只是将它用作指针,它不会抱怨 . 当然,您必须在实现文件中#import它(如果您要实例化该类的对象),因为它需要知道类内容以实例化对象 .

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

    试试这个,在A.h中导入A.h,在A.h中导入B.h.没有任何问题或投诉,它也可以正常工作 .

    When to use @class

    仅当您甚至不想在 Headers 中导入 Headers 时才使用@class . 这可能是您甚至不知道该课程将会是什么的情况 . 你甚至可能没有该课程 Headers 的情况 .

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

  • 5

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

    将#import视为复制粘贴 .

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

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

  • 7

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

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

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

相关问题