在 Objective-C 中,围绕 #import
顺序存在一些微妙的问题。你可能不相信我,直到你尝试在新项目中重复使用旧代码。
在 狂野的 #import! 一文中,我们探讨了 #import
指令过多带来的问题。但导入的太少也有可能导致头文件不好,特别是如果你没有注意 .m 文件中的 #import
顺序。
使导入最少化和完整化
在导入时,头文件应满足这两个条件:
- 应尽量少
- 应尽量完整
"最少 "仅表示头文件导入的内容不应超过其需要。
"完整 "是指头文件导入编译所需的所有内容。考虑一下:
#import "foo.h"
#import "bar.h"
如果删除 foo.h
(或改变顺序)导致 bar.h 无法编译,那么 bar.h
并不完整。
发现不完整的 Header
依赖预编译头文件是导致头文件不完整的一种情况。特别是,预编译的头文件包含某个特定的头文件,并不意味着你可以在其他地方省略它。
另一种头文件不完整的情况是 #import
顺序不当,掩盖了依赖关系。在基于 C 的语言中,程序员在开始编写实现文件时,通常会在最大范围内包含最通用的头文件。然后依次向下,直到包含最具体的头文件:
- 1、系统头文件
- 2、其他头文件
- 3、最后,该文件自身的头文件
这是一种倒退。考虑一下依赖于 <QuartzCore/QuartzCore.h>
的头文件 foo.h
。如果 foo.m
首先导入 QuartzCore
,然后导入其他内容,最后才导入自己的头文件,那么你可能就不会觉得有必要在 foo.h
中导入 QuartzCore
了。......而下一个程序员如果直接导入 foo.h
,就会导致程序崩溃。
原作者在评论答疑中对于此处的解释:
#import
is a preprocessor directive. It’s effectively the same as copying and pasting. So if you import QuartzCore first, and your “self” header last, it all expands in your .m before it’s compiled. That’s why the ordering matters.But if it’s expanded before other headers that use it, those headers will pick it up by accident, not by design. By importing it at the end, any headers that need it but don’t import it themselves will cause a compile-time error. Which is what I want. I want the compiler to tell me about headers that don’t declare their dependencies.
Hopefully this will gradually become history with modules and @import.
#import
是一个预处理器指令。它实际上与复制和粘贴相同。因此,如果你先导入 QuartzCore,最后才导入自己头文件,那么在编译之前,所有文件都会在 .m 中展开。这就是为什么顺序很重要。但是,如果在使用它的其他头文件之前展开它,这些头文件就会意外而非有意地使用它。如果在末尾导入,任何需要它但自己没有导入的头文件都会导致编译时出错。这正是我想要的。我希望编译器能告诉我那些没有声明其依赖关系的头文件。
希望随着模块(modules)和 @import 的使用,这个问题会逐渐成为历史。
好的 #import 顺序
信息披露:以下书籍链接为联盟链接。如果您购买任何商品,我将赚取佣金,您无需支付额外费用。
解决办法很简单:颠倒顺序!从最具体的开始,然后再到最一般的。最重要的是,先包含你自己的头文件。约翰-拉科斯(John Lakos)所著的《大型 C++ 软件设计》是我所知道的唯一一本关于 "物理设计"——如何将源代码编排到文件中的书。
该书作者指出:
Latent usage errors can be avoided by ensuring that the .h file of a component parses by itself—without externally-provided declarations or definitions… Including the .h file as the very first line of the .c file ensures that no critical piece of information intrinsic to the physical interface of the component is missing from the .h file (or, if there is, that you will find out about it as soon as you try to compile the .c file).
将 .h 文件作为 .c 文件的第一行,可以确保 .h 文件中不会缺少组件物理接口的关键信息(如果缺少,也不会在编译 .c 文件时发现)。
我是这么做的。如果我在编写 foo.m
,我会首先导入 foo.h
,并用空行将其与其他导入内容隔开。然后再按顺序导入其他所有导入文件:
#import "foo.h"
#import "abc.h"
#import "def.h"
#import <Abc/Abc.h>
排序可以帮助我找到重复的内容。它还会把角括号导入 <> 放在引号导入之后,这样最一般的标题就会放在最后。