import 和include#include
做的事情其实就是简单的复制粘贴,将目标.h文件中的内容一字不落地拷贝到当前文件中,并替换掉这句include,而#import
实质上做的事情和#include
是一样的,只不过OC为了避免重复引用可能带来的编译错误(这种情况在引用关系复杂的时候很可能发生,比如B和C都引用了A,D又同时引用了B和C,这样A中定义的东西就在D中被定义了两次,重复了),而加入了#import
,从而保证每个头文件只会被引用一次。
实质上import也还是拷贝粘贴,这样就带来一个问题:当引用关系很复杂,或者一个头文件被非常多的实现文件引用时,编译时引用所占的代码量就会大幅上升(因为被引用的头文件在各个地方都被copy了一遍)。为了解决这个问题,C系语言引入了预编译头文件(PreCompiled Header),将公用的头文件放入预编译头文件中预先进行编译,然后在真正编译工程时再将预先编译好的产物加入到所有待编译的Source中去,来加快编译速度。比如iOS开发中Supporting Files组内的.pch文件就是一个预编译头文件,默认情况下,它引用了UIKit和Foundation两个头文件–这是在iOS开发中基本每个实现文件都会用到的东西。
于是理论上说,想要提高编译速度,可以把所有头文件引用都放到pch中。但是这样面临的问题是在工程中随处可用本来不应该能访问的东西,而编译器也无法准确给出错误或者警告,无形中增加了出错的可能性。
模块(Modules)
于是Modules诞生了。Modules相当于将框架进行了封装,然后加入在实际编译之时加入了一个用来存放已编译添加过的Modules列表。如果在编译的文件中引用到某个Modules的话,将首先在这个列表内查找,找到的话说明已经被加载过则直接使用已有的,如果没有找到,则把引用的头文件编译后加入到这个表中。这样被引用到的Modules只会被编译一次,但是在开发时又不会被意外使用到,从而同时解决了编译时间和引用泛滥两方面的问题。
稍微追根问底,Modules是什么?其实无非是对框架进行了如下封装,拿UIKit为例:
framework module UIKit {
umbrella header "UIKit.h"
module * {export *}
link framework "UIKit"
}
这个Module定义了首要头文件(UIKit.h),需要导出的子modules(所有),以及需要link的框架名称(UIKit)。需要指出的是,现在Module还不支持第三方的框架,所以只有SDK内置的框架能够从这个特性中受益。另外,在C++的源代码中,Modules也是被禁用的。
怎么用Module呢
关于普通开发者使用的这个新特性的方法,Apple在LLVM5.0(也就是Xcode5带的最新的编译器前端中)引入了一个新的编译符号@import,使用@符号将告诉编译器去使用Modules的引用形式,从而获取好处,比如想引用MessageUI,可以写成
@import MessageUI;
在使用上,这将等价于以前的#import <MessageUI/MessageUI.h>
,但是将使用Modules的特性。如果只想使用某个特性的.h文件,比如#import <MessageUI/MFMailComposeViewController.h>
,对应写作
@import MessageUI.MFMailComposeViewController;
当然,如果对于以前的工程,想要使用新的Modules特性,如果要把所有头文件都这样一个一个改成@import的话,会是很大的一个工作量。Apple自然也考虑到了这一点,于是对于原来的代码,只要使用的是iOS7或者MacOS10.9的SDK,在Build Settings中将Enable Modules(C and Objective-C)打开,然后保持原来的#import写法就行了。是的,不需要任何代码上的改变,编译器会在编译的时候自动地把可能的地方换成Modules的写法去编译的。
然后讲一下xcode引入系统库和第三方库的方式。
举个例子,有人音频编码的时候
#import <AudioToolbox/AudioToolbox.h>
注释掉上面这句引入后,代码可以正常通过编译,寻找原因。猜测是不是跟·Foundation·和UIKit一样,不用导入xcode和import 就可以正常使用。因为xcode创建一个项目时,有些默认库是自动包含在内的。
Modules 第一次出现在2012年开发者大会上,它对库的封装比以往任何时候更加清洁。不需要预处理逐行的用文件所有内容替换#import指令。一个Modules包含了一个库到自包含的块中。就像pch文件预编译一样,提升了编译速度,也不需要在pch文件中声明你要引入的库。
一个Modules不仅告诉编译器哪些头文件组成了Modules,而且还告诉编译器什么需要链接。这个就解救了你不用你去手动的链接框架。这虽然是一件小事,但是能让开发更加简单就是一件好事。
怎样使用Modules
Modules的使用相当简单。对于存在的工程,第一件事情就是使这个功能生效。你可以在项目的Build Settings通过搜索Modules找到这个选项,改变Enable Modules 选项为YES,像这样:
所有的新工程都是默认开启这个功能的,但是你应该在你所有存在的工程内都开启这个功能。
Autolinking
Autolinking是Modules的附赠小惊喜,因为在module定义的时候指定来link framework,所以在编译module时LLVM会将所涉及到的框架自动帮你写到link里去,不再需要到编译设置里去添加了。即开发者不需要手动建立框架关联,引入头文件将为相应的框架建立自动关联。
Link Frameworks Automatically:这个选项就是用来开启或者关闭自动连接框架功能的,默认是开启的,如果一旦关闭这个选项,你的工程就会报一大堆的错误了,用户可以试一试,关闭这个功能,然后你必须手动导入一个一个框架,这对于开发者来说,无疑是一个灾难性的事件。
framework之Embed、Signing
当我们点击xcode工程的target时,可以看到在Build Phases一样看到framework可选的状态有几种:在xcode 10及之前,有三种状态:Do Not Embed、Embed & Sign和Embed Without Signing
Embed:嵌入,用于动态库,动态库在运行时链接,所以它们需要被打进bundle里面。如何判断呢?使用终端执行:
file frameworkToLink.framework/frameworkToLink
//pwd 的地址是包含frameworkToLink.framework的文件夹
如果返回:
current ar archive
:说明是静态库,选择Do not embed
Mach-0 dynamically
:说明是动态库,选择Embed
静态库和动态库的区别
- 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝,存在形式:.a和.framework
- 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。存在形式:.dylib和.framework
系统的.framework是动态库,我们自己建立的.framework一般是静态库。
*Signing:只用于动态库,如果已经有签名了就不需要再签名。如何判断呢?使用终端执行:
codesign -dv frameworkToLink.framwork
如果返回:
code object is not signed at all
或者adhoc
:选择Embed and sign
- 其它:表示已经正确签名,选择
Embed Without Signing
参考:写iOS程序时可以不用导入Foundation和UIKit框架的原因