iOS工程06头文件PCH及module原理

3,248 阅读4分钟

这章我们继续探索,日常最常用的引入头文件,pch文件及module原理。

一、头文件与PCH

1、引入头文件

一般引入头文件的方法有两种:#include 和 #import
常见问题:#include 和 #import有什么区别呢?

  • #include:会把.h文件直接copy到你使用的.m中(header search path=一组目录+vc.h=完整路径)
  • #import:和include的区别是,唯一性已经引入过的不会在引入了。(import = include + pargam once = 唯一完整路径)

image.png image.png 大概复习一下编译过程如上图:(如需了解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进来。

我们来手动编译一次: image.png

clang -x c-header -c ./Zoo.pch - o ./Zoo.pch.gch

运行命令查看生成产物: image.png 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 只有预编译阶段。

查看生成文件: image.png

LLVM IR

  • Bitcode (.bc)
  • Text Format (.ll)
  • 这两种格式的文件可以相互转换:
  • llvm-dis: .bc -> .ll
  • llvm-asm: .ll -> .bc
  • 两种文件都是全量无损文件,可在此阶段进行优化
  • 如果需要做优化,或者修改bitcode,或者编写pass,都需要熟悉IR

clang : 默认运行的是内部的driver

-cc1:前端选项仅供 clang 开发人员使用。用户不应该直接运行,因为不能保证选项是稳定的

Header

  1. pch:Precompiled Header,全局可⻅性,不需要导⼊
  2. modulemap:描述了⼀组Precompiled Header,需要导⼊你需要的

image.png

要现实优化把.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

image.png .idx 就是记录各个模块pcm的顺序,安idx的记录顺序加载。

优化要点

  1. 将App代码尽量分散到组件中,减少pch文件的使用
  2. 尽量使用framework,自动生成
  3. 对.a开启module
  4. 对遗漏的无法正常使用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

编译运行通过: image.png

总结

  • #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文件。