@import和#import的区别 - iOS 7

461

我正在玩弄一些新的iOS 7功能,并使用WWDC视频“在iOS上实现引人入胜的UI”中讨论的一些图像效果。为了在会话的源代码中产生模糊效果,通过导入UIKit来扩展UIImage类别:

@import UIKit;

我记得在另一个会议视频中看到有关此事的内容,但我很难找到它。我正在寻找任何关于何时使用这个的背景信息。它只能与苹果框架一起使用吗?使用这个编译指令的好处足以让我回头更新旧代码吗?


https://dev59.com/ql0b5IYBdhLWcg3wA9LI - Uday Sravan K
很遗憾,对于 .mm 文件或者说 Objective-C++,目前还不支持使用 @import(我已经尝试过使用 g++clang++ 进行测试,但是并没有成功)。 - Top-Master
6个回答

870
这是一个名为“模块”或“语义导入”的新功能。在WWDC 2013205404会议中有更多信息。它是预编译头文件的更好实现。你可以在iOS 7和Mavericks中使用任何系统框架与模块。模块将框架可执行文件和其标头打包在一起,被称为比#import更安全和高效。
使用@import的一个重要优点是无需在项目设置中添加框架,它会自动完成。这意味着您可以跳过点击加号搜索框架(金色工具箱)并将其移动到“框架”组的步骤。它将使许多开发人员免受神秘的“链接器错误”消息的困扰。 您实际上不需要使用@import关键字。如果您选择使用模块,所有的#import#include指令都会自动映射到使用@import。这意味着您不必更改源代码(或从其他地方下载的库的源代码)。据说使用模块也可以提高构建性能,特别是如果您没有很好地使用PCHs或者您的项目有许多小的源文件。

大多数苹果框架(UIKit、MapKit、GameKit等)都预先构建了模块。您可以将它们用于自己创建的框架:如果您在Xcode中创建一个Swift框架,它们会自动创建,您还可以手动为任何苹果或第三方库创建“.modulemap”文件。

您可以使用代码完成来查看可用框架列表:

enter image description here

Xcode 5中的新项目默认启用模块。要在旧项目中启用它们,请进入项目构建设置,搜索“模块”,并将“启用模块”设置为“是”。 "链接框架"也应该是“是”:

您需要使用Xcode 5和iOS 7或Mavericks SDK,但仍然可以发布到旧的操作系统(例如iOS 4.3或其他版本)。模块不会改变您的代码构建方式或任何源代码内容。

来自WWDC幻灯片:

  • 导入完整的框架语义描述
  • 无需解析头文件
  • 更好的导入框架接口的方法
  • 加载二进制表示
  • 比预编译头文件更灵活
  • 免受本地宏定义的影响(例如#define readonly 0x01
  • 默认情况下启用新项目

显式地使用模块:

#import <Cocoa/Cocoa.h> 替换为 @import Cocoa;

您也可以使用以下表示法导入一个头文件:

@import iAd.ADBannerView;

Xcode中的子模块会自动完成。


15
@DaveDeLong和Klaas:谢谢!我必须承认,当我第一次回答这个问题时,我对模块一无所知。我去看了404会议来学习它。Doug Gregor(LLVM的人)做的演讲非常好。还有一个关于C ++模块的讲座,在这里解释了优势:http://www.youtube.com/watch?v=4Xo9iH5VLQ0。 - nevan king
3
@nevan-- 感谢你的答案。我想补充一下,目前模块还不支持第三方和自己的框架。 - jamdaddy25
1
你能否将此用于你自己的类中? - cfischer
5
如果提供了适当的module.map文件,我认为你应该能够使用@import导入第三方框架。有关LLVM clang模块文档,请参阅:http://clang.llvm.org/docs/Modules.html#module-map-language - bames53
1
哦,实际上看起来 @import sqlite3 对我有效,因为我已经为它创建了自己的 module.map。当我意识到 sqlite 已经包含在 OS X 中并删除了我的 module.map 时,编译器继续使用旧的模块。 - bames53
显示剩余16条评论

51

你可以在《Learning Cocoa with Objective-C》这本书中找到不错的答案(ISBN: 978-1-491-90139-7)。

模块是一种将文件和库包含和链接到项目中的新方法。为了理解模块如何工作以及它们的好处,重要的是回顾Objective-C和 #import语句的历史。每当您想要包含一个文件供使用时,通常会有像下面这样的代码:

#import "someFile.h"

或者在框架的情况下:

#import <SomeLibrary/SomeFile.h>
因为Objective-C是C编程语言的超集,#import声明只是C语言中#include声明的一个小改进。#include声明非常简单;它在编译期间将包含文件中的所有内容复制到您的代码中。这有时可能会导致重大问题。例如,假设您有两个头文件:SomeFileA.hSomeFileB.h; SomeFileA.h包含SomeFileB.h,而SomeFileB.h包含SomeFileA.h。这会创建一个循环,并且可能会使编译器混淆。为了解决这个问题,C程序员必须编写防止此类事件发生的保护措施。
使用#import时,您不需要担心此问题或编写头文件保护以避免出现此问题。然而,#import仍然只是一种强化的复制粘贴操作,会导致缓慢的编译时间以及许多其他较小但仍然非常危险的问题(例如,已经在您的代码中声明的某些内容被包含的文件覆盖)。
模块是绕过这个问题的一种尝试。它们不再是复制并粘贴到源代码中,而是包含文件的序列化表示形式,可以在需要时和地点导入到您的源代码中。使用模块,代码通常会编译得更快,并且比使用#include或#import更安全。
回到导入框架的先前示例:
#import <SomeLibrary/SomeFile.h>

为将这个库作为一个模块导入,代码需要改为:

@import SomeLibrary;

这样做的额外好处是Xcode会自动将SomeLibrary框架链接到项目中。模块还允许您仅将您真正需要的组件包含到您的项目中。例如,如果您想在AwesomeLibrary框架中使用AwesomeObject组件,通常情况下您必须导入所有内容才能使用其中一个部分。然而,使用模块,您只需导入您想要使用的特定对象:

@import AwesomeLibrary.AwesomeObject;

Xcode 5中默认启用了模块,对于所有新项目都是如此。如果你想要在旧项目中使用模块(你确实应该这样做),则必须在项目的构建设置中启用它们。一旦这样做,您可以在代码中同时使用#import@import语句而不必担心任何问题。


我在我的项目(Xcode 6)中没有选项,可以启用模块,但我最初在 Xcode 4 上启动。我是否可以手动添加它? - Awesome-o
构建目标是iOS 6,我认为这就是问题所在。 - Awesome-o

7

@import模块(ObjC)或语义导入

而不是使用通常的模块

//as example
#include <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

历史:

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

它是LLVM模块的一部分。

@import <module_name>;声明告诉编译器要加载(而不是编译)一个预编译模块的二进制文件,从而减少构建时间。以前,每次运行时编译器都会编译依赖项,但现在应该事先编译并仅加载。

//previously
run into dependency -> compile dependency
run into dependency -> compile dependency

//@import
compile dependency 
    run into dependency -> load compiled binary
    run into dependency -> load compiled binary

[模块映射] - 模块和头文件之间的桥梁

Xcode

启用模块 (C 和 Objective-C) (CLANG_ENABLE_MODULES) - CLANG 的#include, #import指令会自动转换为@import,带来所有的优势。因为包含头文件和子/模块之间的映射,所以Modulemap可以无缝地完成这项工作。

传递-fmodules

#include, #import -> @import

Link Frameworks Automatically(CLANG_MODULES_AUTOLINK) - 启用系统模块自动链接。需要激活CLANG_ENABLE_MODULES。自动链接允许基于#import,@import(Objective-C),import(Swift)传递-framework <framework_name>

如果是NO- 会传递-fno-autolink标志

CLANG_ENABLE_MODULES == NO and CLANG_MODULES_AUTOLINK == NO

如果您想要手动处理系统(#import <UIKit/UIKit.h>)链接(而不是自动链接),您有两个选择:

  1. 将依赖项添加到General -> Frameworks and Libraries 或 Frameworks, Libraries, and Embedded Content

  2. Build Settings -> Other Linker Flags(OTHER_LDFLAGS) -> -framework <module_name>

如果出现以下情况,将会抛出下一个错误:

Undefined symbol: _OBJC_CLASS_$_UIView

Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_UIView", referenced from:
      objc-class-ref in ClassB.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1
  • CLANG_ENABLE_MODULES 已禁用
  • CLANG_MODULES_AUTOLINK 已禁用且没有手动链接

逆向工程

otool -l <binary> 
//-l print the load commands
//find LC_LINKER_OPTION
//cmd LC_LINKER_OPTION

4

目前仅适用于内置系统框架。如果您像苹果一样在应用委托中使用#import导入UIKit框架,则会被替换(如果模块打开并且已识别为系统框架),编译器将重新映射它为模块导入而不是头文件的导入。 因此,保留#import与其转换为模块导入后几乎相同。


2

是的,我也遇到了这个问题,但将其设置为“否”会消除所有警告。这样做会有什么副作用吗? - Satheesh

1
使用模块有一些好处。除非创建了模块映射,否则您只能将其与苹果的框架一起使用。@import 与添加到 .pch 文件时预编译头文件有些相似,这是调整应用程序编译过程的一种方式。此外,您不必以旧的方式添加库,实际上使用 @import 更快更有效。如果您仍在寻找一个好的参考资料,我强烈推荐阅读this article

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