【问题标题】:@class vs. #import@class 与 #import
【发布时间】:2010-09-24 06:27:33
【问题描述】:

据我了解,如果 ClassA 需要包含 ClassB 标头,而 ClassB 需要包含 ClassA 标头以避免任何循环包含,则应该使用前向类声明。我也知道#import 是一个简单的ifndef,因此包含只发生一次。

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

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

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

【问题讨论】:

  • 前向声明只是告诉编译器,“嘿,我知道我在声明你不认识的东西,但是当我说@MyClass时,我保证我会在实现中#import它"。

标签: objective-c cocoa cocoa-touch


【解决方案1】:

如果您看到此警告:

警告:接收器“MyCoolClass”是一个转发类,对应的@interface 可能不存在

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

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

举例

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

MyCoolClass *myObject;

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

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

【讨论】:

  • 很好的答案,谢谢。供将来参考:这也处理您在 .h 文件中 @class 某些东西,但忘记在 .m 中 #import 它的情况,尝试访问 @classed 对象上的方法,并收到警告喜欢:warning: no -X method found.
  • 如果 .h 文件包含类接口所需的数据类型或其他定义,则需要 #import 而不是 @class 的情况。
  • 这里没有提到的另一个很大的优点是快速编译。请参考 Venkateshwar 的回答
  • @BenGottlieb “myCoolClass”中的“m”不应该是大写的吗?如“MyCoolClass”?
【解决方案2】:

三个简单的规则:

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

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

【讨论】:

  • 在头文件中,您可能还必须 #import 任何定义您的类采用的协议的内容。
  • 在h接口文件和m实现文件中声明#import有区别吗?
  • 如果你使用类中的实例变量,还有#import
  • @Mark - 被规则 #1 覆盖,即使这样,也只能访问超类中的 ivars。
  • @Tyler 为什么不转发协议声明?
【解决方案3】:

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

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

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

我希望这会有所帮助。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      通常的做法是在头文件中使用@class(但您仍然需要#import 超类),并在实现文件中使用#import。这将避免任何圆形夹杂物,而且它很有效。

      【讨论】:

      • 我认为#import 比#Include 更好,因为它只导入一个实例?
      • 是的。不知道这是否与循环包含有关,或者顺序不正确,但我冒险摆脱了该规则(在标题中进行了一次导入,子类的实现中不再需要导入),很快它就变得非常混乱。最重要的是,遵循该规则,编译器会很高兴。
      • current docs#import “就像 C 的 #include 指令,只是它确保同一个文件不会被多次包含。”因此,根据#import 处理循环包含,@class 指令对此没有特别帮助。
      【解决方案6】:

      另一个优点:快速编译

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

      【讨论】:

        【解决方案7】:

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

        简单的回答:你#import#include 当有物理依赖时。否则,您使用前向声明(@class MONClassstruct 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>)。这可能会引入依赖关系,但库维护人员通常可以根据需要为您处理物理依赖关系——如果他们引入了一项功能,他们可以最大限度地减少它对您的构建的影响。

        【讨论】:

        • 顺便说一句,努力解释这些事情。 .但它们似乎很复杂。
        • NSObject types are never values -- NSObject types are always reference counted pointers. 不完全正确。块在你的答案中抛出一个漏洞,只是说。
        • @RichardJ.RossIII ……GCC 允许声明和使用值,而 clang 禁止它。当然,指针后面一定有值。
        【解决方案8】:

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

        那么:为什么你应该在你的标题中 @class 和 #import 只在你的实现中?由于必须一直 @class #import,您的工作量会加倍。除非你使用继承。在这种情况下,您将为单个@class 多次#importing。然后,如果您突然决定不再需要访问声明,则必须记住从多个不同的文件中删除。

        由于#import 的性质,多次导入同一个文件不是问题。 编译性能也不是真正的问题。如果是这样,我们几乎不会在我们拥有的每个头文件中都使用#importing Cocoa/Cocoa.h 之类的东西。

        【讨论】:

        • 请参阅上面的 Abizem 的回答,以获取文档中关于为什么要这样做的示例。它的防御性编程适用于当您有两个使用另一个类的实例变量相互导入的类头时。
        【解决方案9】:

        如果我们这样做

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

        【讨论】:

          【解决方案10】:

          有关文件依赖项和#import 和@class 的更多信息,请查看:

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

          文章摘要

          头文件中的导入:

          • #import 您要继承的超类以及您要实现的协议。
          • 前向声明其他所有内容(除非它来自框架 带有主标题)。
          • 尝试消除所有其他#imports。
          • 在自己的标头中声明协议以减少依赖性。
          • 前向声明太多?你有一个大型班级。

          在实现文件中导入:

          • 消除不使用的杂乱无章的#imports。
          • 如果一个方法委托给另一个对象并返回它所得到的 返回,尝试转发声明该对象而不是#importing它。
          • 如果包含一个模块会强制您逐级包含 连续的依赖关系,你可能有一组想要 成为图书馆。将其构建为带有 master 的单独库 标头,因此所有内容都可以作为单个预构建块引入。
          • #import 太多?你有一个大型班级。

          【讨论】:

            【解决方案11】:

            当我发展时,我脑子里只有三件事不会给我带来任何问题。

            1. 导入超类
            2. 导入父类(当您有孩子和父母时)
            3. 在您的项目之外导入类(例如在框架和库中)

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

            【讨论】:

              【解决方案12】:

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

              你的第一个念头大概是#import吧。
              在某些情况下,这可能会导致问题。

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

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

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

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

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

              【讨论】:

                【解决方案13】:

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

                编译器会知道有你在头文件中使用的名称来声明的类。

                【讨论】:

                • 你能说得更具体一点吗?
                【解决方案14】:

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

                例如:

                1. 这可能就像您要从中派生类或
                2. 如果您打算将该类的对象作为成员变量(尽管很少见)。

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

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

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

                何时使用@class

                仅当您甚至不想在标题中导入标题时才使用@class。这可能是您甚至不知道该类将是什么的情况。您甚至可能还没有该类的标题的情况。

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

                【讨论】:

                  【解决方案15】:

                  将@class 视为告诉编译器“相信我,它存在”。

                  将#import 视为复制粘贴。

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

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

                  【讨论】:

                    【解决方案16】:

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

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

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

                    【讨论】:

                      猜你喜欢
                      • 2011-11-14
                      • 1970-01-01
                      • 2013-08-01
                      • 2020-02-10
                      • 1970-01-01
                      • 2013-09-27
                      • 2012-03-15
                      相关资源
                      最近更新 更多