关于C#:@class vs. #import

@class vs. #import

据我所知,如果class a需要包含classb头,而classb需要包含classa头,则应该使用forward类声明,以避免任何循环包含。我也理解,#import是一个简单的ifndef,所以include只发生一次。

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

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

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


如果看到此警告:

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

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

@class通常不会消除对#import文件的需求,它只是将需求向下移动到信息有用的地方。

例如

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

1
MyCoolClass *myObject;

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

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


三条简单规则:

  • 在头文件(.h文件)中,只有超级类#import和采用的协议。
  • #import所有类和协议,您将消息发送到正在实现的(.m文件)。
  • 转发其他所有内容的声明。

如果您在实现文件中进行了转发声明,那么您可能会出错。


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

在"定义类类接口"一节中,它描述了为什么要这样做:

The @class directive minimizes the amount of code seen by the compiler and linker, and is therefore the simplest way to give a forward declaration of a class name. Being simple, it avoids potential problems that may come with importing files that import still other files. For example, if one class declares a statically typed instance variable of another class, and their two interface files import each other, neither class may compile correctly.

我希望这有帮助。


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

例外情况是您应该在头文件中继承一个类或正式协议(在这种情况下,您不需要在实现中导入它)。


通常的做法是在头文件中使用@class(但仍然需要导入超类),并在实现文件中导入。这样可以避免任何圆形的杂质,而且它只是起作用。


另一个优势:快速编译

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


My inquiry is this. When does one use #import and when does one use @class?

简单的回答:当存在物理依赖关系时,您使用#import#include。否则,您将使用转发声明(@class MONClassstruct MONStruct@protocol MONProtocol)。

以下是物理依赖的一些常见例子:

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

Sometimes if I use a @class declaration, I see a common compiler warning such as the following:
"warning: receiver 'FooController' is a forward class and corresponding @interface may not exist."

编译器在这方面实际上非常宽容。它将删除提示(如上面的提示),但如果您忽略了它们,并且不正确地执行#import,则可以很容易地将堆栈丢弃。尽管它应该(IMO),编译器并不会强制执行这一点。在ARC中,编译器更严格,因为它负责引用计数。当编译器遇到您所调用的未知方法时,它会回到默认值。假定每个返回值和参数都是id。因此,您应该消除代码库中的每个警告,因为这应该被视为物理依赖。这类似于调用未声明的C函数。对于c,参数假定为int

您倾向于向前声明的原因是,您可以通过因子减少构建时间,因为存在最小的依赖性。使用forward声明,编译器可以看到有一个名称,并且在没有物理依赖关系的情况下,可以正确地分析和编译程序,而不必看到类声明或其所有依赖关系。干净的建筑需要更少的时间。增量构建花费的时间更少。当然,您最终会花费更多的时间来确保您所需要的所有头文件对每个翻译都是可见的,但是这会在缩短构建时间(假设您的项目不是很小)中很快得到回报。

如果您使用#import#include,那么您在编译器上投入的工作比需要的要多得多。您还引入了复杂的头依赖项。你可以把它比作蛮力算法。当您#import时,您拖入了大量不必要的信息,这需要大量的内存、磁盘I/O和CPU来解析和编译源。

objc对于基于C语言的依赖关系非常理想,因为NSObject类型从来不是值——NSObject类型总是引用计数指针。因此,如果您适当地构造程序的依赖项,并且在可能的情况下向前推进(因为所需的物理依赖性非常小),那么您就可以摆脱令人难以置信的快速编译时间。您还可以在类扩展中声明属性,以进一步减少依赖性。对于大型系统来说,这是一个巨大的好处——如果你开发了一个大的C++代码库,你就会知道它的不同之处。

因此,我的建议是在可能的情况下使用远期,然后在存在物理依赖的情况下使用#import。如果你看到这个警告或者其他暗示身体依赖的警告——把它们全部修复。修复方法是在您的实现文件中对#import进行修复。

当您构建库时,您可能会将一些接口分类为一个组,在这种情况下,您将#import引入物理依赖的库(例如#import )。这可以引入依赖性,但是库维护人员通常可以根据需要为您处理物理依赖性——如果他们引入了一个特性,他们可以最小化它对构建的影响。


我看到很多"这样做",但我看不到"为什么?"

那么:为什么要在头中@class,并且只在实现中导入?你的工作翻了一番,因为你一直需要@class和导入。除非你利用遗产。在这种情况下,您将为一个@class导入多次。如果突然决定不再需要访问声明,那么必须记住从多个不同的文件中删除。

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


如果我们这样做

1
@interface Class_B : Class_A

也就是说,我们将类_a继承到类_b中,在类_b中,我们可以访问类_a的所有变量。

如果我们这样做

1
2
3
#import ....
@class Class_A
@interface Class_B

这里我们说我们在程序中使用了class_a,但是如果我们想在class_b中使用class_a变量,我们必须在.m文件中导入class_a(创建一个对象并使用它的函数和变量)。


有关文件依赖项的额外信息,请查看:

http://qualitycoding.org/file-dependencies/这是好东西

文章摘要

imports in header files:

  • #import the superclass you’re inheriting, and the protocols you’re implementing.
  • Forward-declare everything else (unless it comes from a framework
    with a master header).
  • Try to eliminate all other #imports.
  • Declare protocols in their own headers to reduce dependencies.
  • Too many forward declarations? You have a Large Class.

imports in implementation files:

  • Eliminate cruft #imports that aren’t used.
  • If a method delegates to another object and returns what it gets
    back, try to forward-declare that object instead of #importing it.
  • If including a module forces you to include level after level of
    successive dependencies, you may have a set of classes that wants to
    become a library. Build it as a separate library with a master
    header, so everything can be brought in as a single prebuilt chunk.
  • Too many #imports? You have a Large Class.

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

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

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

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

I know you don't know that class, but it exists. It's going to be imported or implemented elsewhere

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

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


当我成长的时候,我心里只有三件事不会给我带来任何问题。

  • 导入超级类
  • 导入父类(当您有子类和父类时)
  • 导入项目外部的类(如框架和库中的类)
  • 对于所有其他类(我的项目中的子类和子类),我通过forward类声明它们。


    把@class想象成告诉编译器"相信我,这存在"。

    将导入视为复制粘贴。

    出于多种原因,您希望最小化导入的数量。如果不进行任何研究,首先想到的是它可以缩短编译时间。

    注意,当您从类继承时,不能简单地使用forward声明。您需要导入文件,这样您声明的类就知道它是如何定义的。


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

    编译器将知道在头文件中有一个名为要声明的类。


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

    前任:

  • 这可能就像你要从中派生出你的类,或者
  • 如果要将该类的对象作为成员变量(尽管很少)。
  • 如果你只是用它作为指针,它不会抱怨。当然,您必须在实现文件中导入它(如果您正在实例化该类的对象),因为它需要知道类内容来实例化对象。

    注意:导入与包含不同。这意味着没有所谓的循环导入。导入是一种要求编译器查找特定文件以获取某些信息的请求。如果该信息已经可用,编译器将忽略它。

    试试这个,把A.H输入B.H,把B.H输入A.H。这样就不会有任何问题或投诉,而且也会很好地工作。

    何时使用@class

    只有当您甚至不想在标题中导入标题时,才使用@class。在这种情况下,你甚至不想知道那门课是什么。可能还没有该类的头的情况。

    例如,您正在编写两个库。一个类名为a,存在于一个库中。此库包含来自第二个库的头。该头可能有一个指针,但可能不需要再次使用它。如果库1尚不可用,则如果使用@class,库B将不会被阻止。但如果您要导入a.h,则会阻止库2的进度。


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // DroneSearchField.h

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