Objective-C中#import和#include有什么区别?

408
在Objective-C中,#import#include有什么区别?是否有时候应该使用其中一个而不是另一个?它们中的哪一个已被弃用了吗?
我正在阅读以下教程:http://www.otierney.net/objective-c.html#preamble ,其中有关#import#include的段落似乎自相矛盾或至少不清楚。
10个回答

387

关于预处理器似乎存在很多混淆。

当编译器遇到 #include 时,它会毫不犹豫地将该行替换为所包含文件的内容。

因此,如果你有一个名为 a.h 的文件,其内容如下:

typedef int my_number;

还有一个名为b.c的文件,其内容如下:

#include "a.h"
#include "a.h"

在编译前,预处理器会将文件 b.c 进行翻译,最终得到:

typedef int my_number;
typedef int my_number;

这将导致编译器错误,因为类型my_number被定义了两次。即使定义相同,在C语言中也不允许这样做。

由于头文件通常在多个地方使用,因此C语言通常会使用include guards。它的写法如下:

 #ifndef _a_h_included_
 #define _a_h_included_

 typedef int my_number;

 #endif
文件b.c在预处理后仍将包含整个头文件的内容两次。但是第二次实例会被忽略,因为宏_a_h_included_已经被定义过了。这种方法非常有效,但有两个缺点。首先,必须编写include guards,并且每个头文件中的宏名称必须不同。其次,编译器仍需要查找头文件并读取它,就像它被包含多少次一样。Objective-C具有#import预处理指令(某些编译器和选项也可用于C和C++代码)。这几乎与#include相同,但它还在内部注明了已经被包含的文件。对于第一次遇到的命名文件,#import行仅被替换为其内容。之后每次遇到它只会被忽略。

6
这个答案比被采纳的答案更好。@Guill,你应该改变被采纳的答案。 - Nguyen Minh Binh
10
在一个有7000行的模板头文件中将4个#include替换为#import后,编译速度明显提高,XCode智能感知响应更快。(我认为这不是我的想象)。 - bobobobo
如果文件包含 #pragma once,那么 #import#include 是否会完全相同? - markoj
包含 包含* - markoj

360
#import指令是Objective-C中# include的改良版。然而,它是否更好仍然存在争议。 #import确保文件仅被包含一次,因此您永远不会遇到递归包含的问题。但是,大多数好的头文件已经对此进行了保护,因此这并没有太多好处。 基本上,使用哪种取决于您。我倾向于使用#import来导入Objective-C的头文件(例如类定义等),并且#include需要的标准C内容。例如,我的某个源文件可能如下所示:
#import <Foundation/Foundation.h>

#include <asl.h>
#include <mach/mach.h>

68
即使头文件包含预编译指令,如果使用了 #include,在编译过程中仍会有性能损失,因为编译器必须打开每个头文件以查看预编译指令。 - Matt Dillard
8
我认为 #import 实际上是由 GCC 而不是 Objective-C 添加的。只要使用 GCC(或 Clang)编译,您就可以在非 ObjC 语言中使用它。 - Dave DeLong
36
@dave - #import 是 Objective-C 添加到预处理器中的功能。GCC 也支持在 C 和 C++ 源文件中使用它,尽管官方建议不要在 C 或 C++ 中使用它,而是使用传统的可移植头文件保护方式。然而,所有的 Objective-C 预处理器都必须包含 #import。 - Jason Coco
13
头文件保护指的是在头文件顶部添加以下代码:#ifndef myheader #define myheader,然后再加上头文件的代码,最后以#endif结束。这样做可以防止头文件被重复包含。 - Tim
9
这不是标准与非标准的问题;而是语言与语言之间的区别以及不同的意图。如果你正在使用 Objective-C 并且打算包含一个 Objective-C 头文件,请使用 #import。如果你正在使用 C、C++ 或者仅想将一个文件内联到另一个文件中使用 Objective-C,则使用 #include。 - Steven Fisher
显示剩余8条评论

70

我同意Jason的观点。

我被抓到做了这件事:

#import <sys/time.h>  // to use gettimeofday() function
#import <time.h>      // to use time() function

对于GNU gcc编译器,它一直在抱怨time()函数未定义。

然后我将#import改为#include,一切都没问题了。

原因:

您使用了#import <sys/time.h>:
    <sys/time.h>仅通过使用#define包含了<time.h>的一部分

您使用了#import <time.h>:
    不行。尽管已经包含了<time.h>的部分内容,但是对于#import来说,该文件现在已经被完全包含了。

底线:

C/C++头文件传统上包含其他头文件的部分
因此,对于C/C++头文件,请使用#include。
对于objc / objc ++头文件,请使用#import。


2
似乎clang没有这个未定义的问题。 - ooops
这是一个非常好的例子,说明为什么以非侵入式的方式向现有系统引入现代功能(#import)是具有挑战性的。是否应该更新系统库以更好地处理#import...可能不应该。如果这样做,那将以许多现有项目为代价,这些项目明知或不知地依赖于现有行为。语言开发是否应该停止引入新的、有用的、前瞻性的功能...不应该。因此,它从来不像接受答案的一些评论所暗示的那样简单。 - rambo
这是一个糟糕的编写包含文件的明显问题。GCC已经承认了这一点,并完全重新构建了他们的头文件。头文件永远不应该依赖于任何包含顺序。 - Lothar

26

#include 和C语言的 #include 用法相同。

#import 可以跟踪已经包含的头文件,如果头文件在编译单元中被引用超过一次,则会被忽略。这使得使用头文件保护变得不必要。

总之,在Objective-C中只需要使用#import,不用担心头文件会被重复引用。


2
假设我一分钟内不熟悉C #include(主要是因为我确实不熟悉),那么#include和#import的主要区别是什么?此外,你能告诉我头文件保护是什么吗? - Ryan Guill
@Ryan:看看Sven的答案。 - Adrian Petrescu

14
我知道这个帖子有些老了……但在“现代时代”中,通过clang的@import模块有一个更加优秀的“包含策略”,这常常被忽视了。

模块通过使用更加健壮、高效的语义模型替换文本预处理器包含模型,提高了对软件库API的访问。从用户的角度来看,代码看起来只有稍微不同,因为使用了导入声明而不是#include预处理指令:

@import Darwin; // Like including all of /usr/include. @see /usr/include/module.map

或者
@import Foundation;  //  Like #import <Foundation/Foundation.h>
@import ObjectiveC;  //  Like #import <objc/runtime.h>

然而,与相应的#include不同,此模块导入的行为非常不同:当编译器看到上面的模块导入时,它会加载模块的二进制表示,并直接向应用程序提供其API。在导入声明之前的预处理器定义对所提供的API没有影响...因为该模块本身是作为独立的模块进行编译的。此外,任何使用模块所需的链接器标志将在导入模块时自动提供。这种语义导入模型解决了预处理器包含模型的许多问题。
要启用模块,请在编译时传递命令行标志-fmodules,也称为CLANG_ENABLE_MODULES,在Xcode中。如上所述,此策略消除了所有LDFLAGS。也就是说,您可以删除任何“OTHER_LDFLAGS”设置以及任何“Linking”阶段。

enter image description here

I find compile/launch times to "feel" much snappier (or possibly, there's just less of a lag while "linking"?).. and also, provides a great opportunity to purge the now extraneous Project-Prefix.pch file, and corresponding build settings, GCC_INCREASE_PRECOMPILED_HEADER_SHARING, GCC_PRECOMPILE_PREFIX_HEADER, and GCC_PREFIX_HEADER, etc.
此外,虽然没有很好的文档说明...您可以为自己的框架创建module.map文件,并以同样方便的方式包含它们。您可以查看我的ObjC-Clang-Modules GitHub存储库,以了解如何实现这些奇迹的一些示例。

5
如果您熟悉C++和宏,那么
#import "Class.h" 

与...类似

{
#pragma once

#include "class.h"
}

这意味着当您的应用程序运行时,您的类只会被加载一次。

这是#pragma once的支持使用吗?我一直认为#pragma需要在被包含的文件内部才能起作用。 - uliwitness
@uliwitness 你是正确的。#pragma once 应该放在被包含的文件中,而不是执行包含的文件中。对此给予 -1。 - herzbube

1

如果在.h文件中两次#include同一个文件,则编译器会报错。 但是,如果多次#import同一个文件,则编译器会忽略它。


8
同一个文件被 #include 两次不会导致错误。 - kennytm
1
为了补充@KennyTM的评论,如果通常的头文件保护(#ifndef FILE_NAME_H #define FILE_NAME_H #end)存在,则在同一头文件中两次#include相同的文件不会导致编译错误。这是预期的做法。使用#import时,不需要头文件保护。 - jbat100
@jbat100: #include只是一个复制和粘贴的机制。有意地多次使用#include而没有包含保护,例如“X宏”。 - kennytm
包含同一个文件可能会导致错误,具体取决于你所包含的内容。我曾经看到过使用 #include 实现一种模板的 C 代码。他们进行了 #define,包含了一个头文件,然后 #undef 并重新进行了 #define,第二次包含相同的头文件。这样可以使代码参数化、有效,并且被包含两次,因为定义的值是不同的。因此,使用 #include 有其优点,但如果你使用像 C++ 或 ObjC 这样的现代语言,通常不需要这样做。 - uliwitness

1

#include是用来从另一个文件中获取"东西"到使用#include的文件中的。

例如:

在文件main.cpp中:

#include "otherfile.h"

// some stuff here using otherfile.h objects,
// functions or classes declared inside

头文件保护(Header guard)用于每个头文件(*.h)的顶部,以防止多次包含同一文件(如果发生这种情况,将会出现编译错误)。

在文件中:otherfile.h

#ifndef OTHERFILE
#define OTHERFILE

// declare functions, classes or objects here

#endif

即使您在代码中多次使用#include "otherfile.h",其中的内容也不会被重新声明。

0
在我的情况下,我在一个 .h 文件中有一个全局变量导致了问题,通过在其前面添加 extern 来解决了这个问题。

-1

#include vs #import 预处理指令

历史:

#include -> #import -> [预编译头文件 .pch] -> [@import 模块(ObjC);] -> [import 模块(Swift)]

#import#include 的下一代,解决了当前 .h 文件的 重复包含递归包含 问题。在当前文件中只有一个被包含的 .h 文件副本。

#import == #include + guard

守卫看起来像

#ifndef <some_unique_name>
#define <some_unique_name>

<header_body>

#endif

#include guard维基百科(宏保护、头文件保护、文件保护)——防止预处理器重复包含头文件,从而减缓构建时间。

#include#import 使用类似复制/粘贴的机制——递归复制 .h 文件中的代码内容(除了 #include#import 指令之外的所有内容)。这意味着结果文件不会包含 #include#import 指令。

您可以通过选择 .m 文件,然后执行 "Product -> Perform Action -> Preprocess ".m"

#include 示例

//A.h
@interface A : NSObject
- (int)startA;
@end

//ViewController.h
#include "A.h"

预处理后的ViewController.m

@interface A : NSObject
- (int)startA;

@end

@interface ViewController : UIViewController
@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

}
@end

双重引用示例

//A.h
@interface A : NSObject //Build time error: Duplicate interface definition for class 'A'
@end 

//B.h
#include "A.h"

//C.h
#include "A.h"
//#import "A.h" to solve
#include "B.h"

递归包含示例

//A.h
#include "B.h" //Build time error: #include nested too deeply
//#import "B.h" to fix it

@interface B : NSObject //Build time error: Duplicate interface definition for class 'A'
@end


//B.h 
#include "A.h" //Build time error: #include nested too deeply
//#import "A.h" to fix it

@interface B : NSObject //Build time error: Duplicate interface definition for class 'B'
@end

[在.h.m中导入]


答案很令人困惑。它没有清楚地回答问题 - “有什么区别?”最后一句话的英语很糟糕,可以改进。答案引用了太多无用的参考资料,其目的不是回答问题,而是连接其他作者的答案,这也引发了问题 - 为什么?下面的批准答案非常清晰,虽然有点老,但这个答案没有任何贡献。 - Alexander Volkov

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