这章我们继续探索,日常最常用的引入头文件,pch文件及module原理。
一、头文件与PCH
1、引入头文件
一般引入头文件的方法有两种:#include 和 #import 。
常见问题:#include 和 #import有什么区别呢?
#include:会把.h文件直接copy到你使用的.m中(header search path=一组目录+vc.h=完整路径)#import:和include的区别是,唯一性已经引入过的不会在引入了。(import = include + pargam once = 唯一完整路径)
大概复习一下编译过程如上图:(如需了解LLVM编译过程的点)
源码通过,词法,语法,语义分析后到的编译过程。
编译器前端(clang)->source code->Lexer->tokens->parser->AST-> Semantic Analysis->Code Generation
编译器后端(LLVM)->LLVM IR(统一IR)->AsmPrinter->Assembler->object code->Linker->execuatble file
问题来了,那么improt导入头文件在编译的那个阶段呢?
在AST之前,的预处理阶段。先导入相关引用头文件,在进行语法,词法,语义分析。
那么如果100个.m文件引入了同一个.h文件,多次import会进入多次编译语词分析流程,这样会消耗编译性能。
那么要如何优化这一过程呢?
Xcode就引入了PCH,预编译头文件来优化,减少冗余过程:预先生成好ast产物。
预编pch命令 :编译产物目录(.gch)[ast产物]文件(ast产物 .h内容) ast产物全局的。但是ast产物还是会被多次copy到.m中,哪能不能优化把.m需要的ast产物copy进来。
我们来手动编译一次:
clang -x c-header -c ./Zoo.pch - o ./Zoo.pch.gch
运行命令查看生成产物:
bitcode含义:1.字节流格式 2.LLVM IR格式 目前生成的gch就是bitcode文件,pcm、gch格式包含了表示AST tree的结构体的bitstream
在LLVM中有专门的工具:llvm-bcanalyzer来分析bitcode格式文件。
我们在来看一个命令:
clang -x c -include-pch include/Zoo.pch.gch -emit-llvm user.m -S
clang --help 查看帮助文档
-S 只有预编译阶段。
查看生成文件:
LLVM IR
- Bitcode (.bc)
- Text Format (.ll)
- 这两种格式的文件可以相互转换:
- llvm-dis: .bc -> .ll
- llvm-asm: .ll -> .bc
- 两种文件都是全量无损文件,可在此阶段进行优化
- 如果需要做优化,或者修改bitcode,或者编写pass,都需要熟悉IR
clang : 默认运行的是内部的driver
-cc1:前端选项仅供 clang 开发人员使用。用户不应该直接运行,因为不能保证选项是稳定的
Header
pch:Precompiled Header,全局可⻅性,不需要导⼊modulemap:描述了⼀组Precompiled Header,需要导⼊你需要的
要现实优化把.m需要的ast产物从pch.gch->copy进来。就要学习module模块。
二、module原理
module的作用:你用哪一块二进制头文件就给你引入哪个二进制头文件;
1、module模块
怎么管理一组头文件呢?创建 module.modulemap文件。
// 怎么管理一组头文件
// Zoo [Cat Zoo]
module Zoo {
// 子module
module Cat {
header "Cat.h" //引用了其他的module
export * //有使用的模块 都暴露 如:#import <Foundation/Foundation.h>
}
module Dog {
header "Dog.h"
}
}
使用创建的module.modulemap文件生成use.o文件
clang -x objective-c -fmodules -fmodule-map-file='./include/module.modulemap' -fmodules-cache-path="./ModuleCache" -c use.m -o use.o
.idx 就是记录各个模块pcm的顺序,安idx的记录顺序加载。
优化要点
- 将App代码尽量分散到组件中,减少pch文件的使用
- 尽量使用framework,自动生成
- 对.a开启module
- 对遗漏的无法正常使用module映射的引入hmap*
看一段module 代码,来了解module的用法。
module Zoo {
umbrella header "NZoo.h"//一个头文件的映射
export *
module * { export * }
}
/**
module *
module Cat
module Dog
*/
NZoo.h 管理两个头文件:
#import "Cat.h"
#import "Dog.h"
那如何在工程中配置使用module文件来编译工程呢?配置.xcconfig
OTHER_CFLAGS = -fmodule-map-file="${SRCROOT}/APP/zoo.modulemap" -fmodules-cache-path="${SRCROOT}/ModuleCache"
GCC_PRECOMPILE_PREFIX_HEADER = YES
GCC_PREFIX_HEADER = prefix.pch
编译运行通过:
总结
#include:会把.h文件直接copy到你使用的.m中(header search path=一组目录+vc.h=完整路径)#import:和include的区别是,唯一性已经引入过的不会在引入了。(import = include + pargam once = 唯一完整路径)
后面我们了解了,pch 预编译头文件。但是pch 引入太多.h也会导致编译速度变慢,如何优化呢?通过我们对llvm编译过程的了解,在编译前端 IR之前,会生成AST产物,我们控制module 的配置可以优化对AST产物的映射。
module:你用哪一块二进制头文件就给你引入哪个二进制头文件;通过clang -x objective-c -fmodules命令会生成.idx和pcm文件投射生成对应.m .o文件。