在Objective-C中如何前向声明枚举?

82

我在Objective-C程序中遇到了枚举类型的可见性问题。我有两个头文件,其中一个定义了typedef enum。另一个文件需要使用这个typedef类型。

在C语言中,我可以简单地用#include来引入另一个头文件,但在Objective-C中,推荐不要使用#import,而是根据需要使用前向@class声明。然而,我无法弄清如何前向声明枚举类型。

我并不需要实际的枚举值,除非在相应的.m实现文件中,在那里我可以安全地使用#import。那么我该如何让typedef enum在头文件中被识别呢?

6个回答

84

最近一种(Swift 3; 2017年5月)在Objective-C中前向声明枚举(NS_ENUM/NS_OPTION)的方法是使用以下内容:

// Forward declaration for XYZCharacterType in other header say XYZCharacter.h
typedef NS_ENUM(NSUInteger, XYZCharacterType);


// Enum declaration header: "XYZEnumType.h"
#ifndef XYZCharacterType_h
#define XYZCharacterType_h

typedef NS_ENUM(NSUInteger, XYZEnumType) {
    XYZCharacterTypeNotSet,
    XYZCharacterTypeAgent,
    XYZCharacterTypeKiller,
};

#endif /* XYZCharacterType_h */`

1
我昨天才开始研究typedef NS_ENUM作为清理旧的Objective C代码的一种方式 - 这个答案对我很有用。 - Greg
@lal,这对于int变量非常好。我刚刚发布了一个关于如何使用typedef enum来处理float变量的问题。希望你能回答它-https://stackoverflow.com/q/44233973/2348597 - Greg
2
如果您在Swift中定义了一个@objc enum并需要在.h文件中使用该类型,这也是有帮助的。您必须以这种方式进行前向声明(查看您的-Swift.h头文件以查看原始类型应该是什么)。 - Max
抱歉,我已经很久没有看过这个问题了。StackOverflow应该在有一个回答的赞数显著高于当前接受的回答时提醒你。我已经接受了这个回答以反映新的最佳实践。 - Stephen Touset

16

回答你的问题是要么导入typedef头文件,要么使用像NSInteger这样的通用类型代替枚举类型。

然而,不导入头文件的原因不仅仅是编译速度。

不导入头文件还可以减少您对额外类的意外访问。

例如,假设您有一个TrackFileChanges类来跟踪特定文件的文件系统更改,并且您有一个CachedFile类来存储来自文件的缓存数据。后者可能会使用私有类型为TrackFileChanges *的实例变量,但对于CachedFile的使用,这只是实现细节(理想情况下,ivar将使用新运行时自动生成具有私有属性的Ivar,但如果您使用旧运行时,这是不可能的)。

因此,导入“CachedFile.h”的客户端可能不需要或不想访问“TrackFileChanges.h”。如果他们确实需要访问,则应通过自己导入它来明确表明。通过在CachedFile.h中使用@class TrackFileChanges而不是#import"TrackFileChanges.h",可以提高封装性。

但是,所有这些说法都不能阻止从第二个头文件导入头文件,如果第二个头文件希望向所有客户端公开第一个头文件。例如,声明类的头文件需要在子类化头文件中直接导入,声明协议的头文件可能需要直接导入(尽管您可以使用@protocol ABC;来避免这种情况)。


8

可以直接使用#import。人们推荐尽可能使用@class仅仅是因为这样可以使你的代码编译速度稍微快一点。但实际上,从一个.h文件中#import另一个.h文件并没有问题。事实上,在扩展另一个类时,你需要这样做。


1
有没有不使用 #import 的方式实现上述内容?那么只需使用 typedef int EnumName 就可以了吗? - Stephen Touset
1
我不这么认为。请查看gs答案中的链接:https://dev59.com/eXVD5IYBdhLWcg3wJIEK - Sebastian Celis
39
人们建议使用 @class 来避免 #import 循环依赖(例如 foo.h 导入了 bar.h,而 bar.h 又导入了 foo.h)。请参见此处的被接受的答案:https://dev59.com/C1_Va4cB1Zd3GeqPV7bR - alexkent
6
更重要的是,@class 可以保护你免受循环导入的影响。 - Ozgur Vatansever
3
对于那些有C/C++背景的人来说,#import是安全的include-guard。请注意,这是对原文的直译,没有任何解释或添加其他内容。 - MattD
显示剩余2条评论

4

如果您可以使用编译器扩展,您可以在Clang中使用以下顺序:

enum Enum;
typedef enum Enum Enum2;

void f(Enum2); // ok. it sees this type's true name.

enum Enum {
    E_1
};

// ok. now its declaration is visible and we can use it.

void f(Enum2 e) {

}

注意:这将触发-Wpedantic警告。
如果您使用的是C++11,应该使用它们的枚举类型,这些枚举类型是安全的可以进行前向声明。例如:enum class Enum:uint8_t;(不是编译器扩展)。

1
你可以将这个答案简化为:typedef enum Enum Enum; 然后在你的方法定义和声明中使用 Enum。 - Sandy Chapman

2
在Objective-C的.h文件中使用前向声明枚举,我采用的方法是查看ProjectName-Swift.h文件里的内容,它包含以下代码:
enum SwiftEnumName : NSInteger;
我需要进行这个前向声明,因为我有一个函数参数类型为SwiftEnumName。但是我不能在Objective-C的.h文件中导入ProjectName-Swift.h文件。
然后,在Objective-C的.m文件中,我只需导入#import "ProjectName-Swift.h",就可以正常使用SwiftEnum了。
这是在Swift 4.1.2中进行的操作。

-1

你必须要么使用 #import 导入它们,要么创建一个仅包含 typedef 的单独头文件。在头文件中不导入其他头文件可以加快编译速度,但不会改变任何其他东西。

为什么 C++ 不支持枚举的前向声明?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接