我不知道为什么,但有时我设法解决了一些编译错误,最值得注意的是
error expected specifier-qualifier-list before 'someClass'
将{。{1}}从.h文件移动到.m文件中。这也解决了我遇到的其他一些与标题相关的问题(从我的观点来看神秘)。
一些粗略的谷歌搜索已经找到答案“从不在头文件中导入标题”,这就是建议停止的地方。
要么我完全做了这件事,要么我已经从某个地方养成了习惯,但我认为标题是要导入标题的地方。显然不是,但任何人都可以向我解释为什么会这样,以及导入标题的首选方式是什么?
答案 0 :(得分:13)
John给出了很好的建议,但这里有更多关于为什么,为什么和例外的背景。
避免将标头导入标头有两个目的:改进增量构建时间和避免循环依赖。如果您将A.h
导入B.h
并将B.h
导入C.h
,则每次更改A.h
中的任何内容时,都必须重新编译C.m
即使C.m
没有使用A.h
中定义的任何内容,也是如此。这可能导致非常糟糕和不必要的构建流失,特别是如果您有经常更改的标头(在开发的早期阶段很常见)。
第一个目标值得称赞,但对于小型项目,谁在乎呢?你有一个四核专业版和一个完整的版本需要几分钟,对吧?但你还是要担心第二个问题:循环依赖。 A.h
引用类B
和B.h
引用类A
。这实际上可能经常发生,并且可以非常无辜地进入系统。集合对象可能引用它包含的对象的类型,并且对象可能引用集合对象的类型。所需要的只是一个引用,因为某些方法接受或返回该类型。如果您有导入其他标题的标题,则发生这种情况的可能性很快就会达到统一。你结束了递归导入,编译时错误可能令人费解。 “我知道这个typdef被定义了!它就在那里!它是进口的!”但是,当您导入此标头时,它尚未解析。这就是导致你的错误的原因。
出于这个原因,即使你不关心构建时间(尽管你应该这么做),也要避免将标题导入标题......除了....
您要导入的某些标头。你的超级课程当然。定义您实施的@protocol
或您使用的typedef
的文件。所以,是的,你必须包括那些。
那么系统标题呢?好吧,它们永远不会导致流失,显然它们不会导致递归导入,所以它们很好。我不鼓励人们对系统标题中的内容使用@class
前向声明。它为您的标题用户创建了额外的工作,没有任何价值。为了获得良好的标题卫生,请记住将系统标题括在<尖括号>中。和你的标题在“引号”。
所以这不是一个微不足道的问题,但简单的规则是:避免在编译器允许的任何时候将用户头导入其他用户头。
答案 1 :(得分:10)
除非您继承自您所包含的类,否则不应在标题中包含标题。如果需要将对象包含为接口变量,则应使用{而是指令{1}};这将告诉编译器标识符引用一个类。
而是仅在实现文件中导入标头。编译器将知道您的实例变量是指向对象的指针,但在解析标头时它不知道对象的详细信息。它需要知道的是它是一个类。然后,编译器可以在解析实现文件时查看类的方法;此时,它确实需要该类,以验证它是否响应了您正在发送的消息。
更新:我打算更新我的回答以回答以后的一些问题,但 Rob Napier has a good follow-up 。
答案 2 :(得分:0)
您不仅要将标题包含在实现文件中,还要包含在使用该类的其他文件中。如果在头文件中包含所有依赖项,则包含该头文件的所有其他文件仅用于使用它定义的特定类,它们本身也包含所有依赖项。
这不仅有噪音,而且当您有自己引用的课程时会导致问题。每个类都必须在另一个类之前导入,这导致首先导入一个类,然后找不到另一个类的类型。这会导致您发布的含义错误消息,这基本上意味着编译器无法找到someClass
类型。
通过将导入移动到您的实现文件中并在标题中转发声明类和类型(使用@class
,@protocol
等),您可以避免这些问题。