代码编译与App运行知识整理
2016-05-05 » iOS性能优化一、Xcode在编译过程中都做了什么
LLVM的架构

iOS编译过程

Objective-C与swift都采用Clang作为编译器前端,编译器前端主要进行语法分析,语义分析,生成中间代码,在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。

编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化,根据不同的系统架构生成不同的机器码。 C++ , Objective C都是编译语言。编译语言在执行的时候,必须先通过编译器生成机器码。
1、Clang-LLVM 下的一个源文件的编译过程
代码转换过程
1、预处理
把宏替换,删除注释,展开头文件,产生 .i 文件。
2、编译
把之前的 .i 文件转换成汇编语言,产生 .s文件。
3、汇编
把汇编语言文件转换为机器码文件,产生 .o 文件。
4、链接
对.o文件中的对于其他的库的引用的地方进行引用,生成最后的可执行文件(同时也包括多个 .o 文件进行 link)。

预处理(Pre-process):他的主要工作就是将宏替换,删除注释展开头文件,生成 .i 文件。
词法分析 (Lexical Analysis):将代码切成一个个 token,比如大小括号,等于号还有字符串等。是计算机科学中将字符序列转换为标记序列的过程。
语法分析(Semantic Analysis):验证语法是否正确,然后将所有节点组成抽象语法树 AST 。由 Clang 中 Parser 和 Sema 配合完成
静态分析(Static Analysis):使用它来表示用于分析源代码以便自动发现错误。
中间代码生成(Code Generation):开始IR中间代码的生成了,CodeGen 会负责将语法树自顶向下遍历逐步翻译成 LLVM IR,IR 是编译过程的前端的输出后端的输入。
优化(Optimize):LLVM 会去做些优化工作,在 Xcode 的编译设置里也可以设置优化级别 (Optimization Level),还可以写些自己的 Pass,官方有比较完整的 Pass 教程: Writing an LLVM Pass — LLVM 5 documentation 。如果开启了 bitcode 苹果会做进一步的优化,有新的后端架构还是可以用这份优化过的 bitcode 去生成。
生成目标文件(Assemble):生成Target相关Object(Mach-o)
链接(Link):生成 Executable 可执行文件
2、执行一次 Xcode build 的流程
1、编译信息写入辅助文件,创建编译后的文件架构 name.app
2、处理文件打包信息
Entitlements:
{
"application-identifier" = "app的bundleid";
"aps-environment" = development;
}
3、执行cocoapod编译前脚本
例如对于使用CocoaPod的工程会执行Check Pods Manifest.lock
4、编译.m文件 使用CompileC和clang命令。
CompileC ClassName.o ClassName.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
export LANG=en_US.US-ASCII
export PATH="..."
clang -x objective-c -arch x86_64 -fmessage-length=0 -fobjc-arc... -Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot iPhoneSimulator10.1.sdk -fasm-blocks ... -I 上文提到的文件 -F 所需要的Framework -iquote 所需要的Framework ... -c ClassName.c -o ClassName.o
5、链接需要的Framework,例如Foundation.framework
6、编译xib文件
7、拷贝xib,图片等资源文件到结果目录
8、编译ImageAssets
9、处理info.plist
10、执行CocoaPod脚本
11、拷贝Swift标准库
12、创建.app文件和对其签名
1、dsym文件
我们在每次编译过后,都会生成一个dsym文件。dsym文件中,存储了16进制的函数地址映射。
在App实际执行的二进制文件中,是通过地址来调用方法的。在App crash的时候,第三方工具(Fabric,友盟等)会帮我们抓到崩溃的调用栈,调用栈里会包含crash地址的调用信息。然后,通过dSYM文件,我们就可以由地址映射到具体的函数位置。
3、指令集,bitcode的作用
指令集
指令集是针对设备不同、处理器不一样来的。
| CPU | iPhone |
|---|---|
| armv6 | iPhone, iPhone 3G |
| armv7 | iPhone 3GS, iPhone4(GSM),iPhone 4(CDMA),iPhone 4S |
| armv7s | iPhone 5, iPhone 5C |
| arm64 | iPhone 5S, iPhone SE, iPhone 6, iPhone 6 Plus, iPhone 6s, iPhone 6s Plus, iPhone 7, iPhone 7 Plus, iPhone 8, iPhone 8 Plus, iPhone X |
| arm64e | iPhone XS, iPhone XS Max, iPhone XR |
iOS模拟器没有arm指令集,iOS模拟器运行的是Mac处理器,运行的是i386|x86_64指令集。
在xcode设置中 Architectures 有明确设置项 , 当我们编译Framework的时候,也会需要设置指令集。我们的代码编译到指定机器上使用,不同指令集是不通用的,所以当我们需要兼容多个指令集设备的时候,需要根据设置的指令集编辑不同的结果,这样在任一的设备都有对应的可执行程序。
当指令集多的时候,编译打包出来的Framework或者可执行程序就会更大 (动态库和静态库具体 是可执行文件 还是 所有 Framework 哪些变大后续再测试 )
bitcode的作用:
Bitcode是编译后的程序的中间表现,包含Bitcode并上传到App Store Connect的Apps会在App Store上编译和链接。包含Bitcode可以在不提交新版本App的情况下,允许Apple在将来的时候再次优化你的App 二进制文件。
对于iOS Apps,Enable bitcode 默认为YES,是可选的(可以改为NO)。对于WatchOS和tvOS,bitcode是强制的。如果你的App支持bitcode,App Bundle(项目中所有的target)中的所有的Apps和frameworks都需要包含Bitcode。
bitcode 开启的时候,代码最终编译成一个中间状态上传AppStore,根据下载到的不同设备再由一个中间状态转为对应的指令集状态。这样就可以节约很大一部分资源,包体积会更小。
https://juejin.im/post/5c17720af265da615304adc0