👨🏻💻 Github Demohtml
整理学习 iOS Principle 一系列的文章,每篇开头归结知识点,帮助记忆前端
2000年,伊利诺伊大学厄巴纳-香槟分校(University of Illinois at Urbana-Champaign 简称UIUC)这所享有世界声望的一流公立研究型大学的 Chris Lattner(他的 twitter @clattner_llvm ) 开发了一个叫做 Low Level Virtual Machine 的编译器开发工具套件,后来涉及范围愈来愈大,能够用于常规编译器,JIT编译器,汇编器,调试器,静态分析工具等一系列跟编程语言相关的工做,因而就把简称 LLVM 这个简称做为了正式的名字。Chris Lattner 后来又开发了 Clang,使得 LLVM 直接挑战 GCC 的地位。2012年,LLVM 得到美国计算机学会 ACM 的软件系统大奖,和 UNIX,WWW,TCP/IP,Tex,JAVA 等齐名。linux
Chris Lattner 生于 1978 年,2005年加入苹果,将苹果使用的 GCC 全面转为 LLVM。2010年开始主导开发 Swift 语言。git
iOS 开发中 Objective-C 是 Clang / LLVM 来编译的。github
Swift 是 Swift / LLVM,其中 Swift 前端会多出 SIL optimizer,它会把 .swift 生成为中间代码 .sil 属于 High-Level IR, 由于 Swift 在编译时就完成了方法绑定,直接经过地址调用属于强类型语言,方法调用再也不是像OC那样的消息发送,这样编译就能够得到更多的信息用在后面的后端优化上。正则表达式
LLVM是一个模块化和可重用的编译器和工具链技术的集合,Clang 是 LLVM 的子项目,是 C,C++ 和 Objective-C 编译器,目的是提供惊人的快速编译,比 GCC 快3倍,其中的 clang static analyzer 主要是进行语法分析,语义分析和生成中间代码,固然这个过程会对代码进行检查,出错的和须要警告的会标注出来。LLVM 核心库提供一个优化器,对流行的 CPU 作代码生成支持。lld 是 Clang / LLVM 的内置连接器,clang 必须调用连接器来产生可执行文件。objective-c
这里是 Clang 官方详细文档: Welcome to Clang’s documentation! — Clang 4.0 documentation 编程
这篇是对 LLVM 架构的一个概述: The Architecture of Open Source Applicationsswift
将编译器以前对于编译的前世此生也是须要了解的,好比回答下这个问题,编译器程序是用什么编译的?看看 《linkers and loaders》这本书就知道了。后端
LLVM 是 Low Level Virtual Machine 的简称,这个库提供了与编译器相关的支持,可以进行程序语言的编译期优化、连接优化、在线编译优化、代码生成。简而言之,能够做为多种语言编译器的后台来使用。若是这样还比较抽象的话,介绍下 Clang 就知道了:Clang 是一个 C++ 编写、基于 LLVM、发布于 LLVM BSD 许可证下的 C/C++/Objective C/Objective C++ 编译器,其目标(之一)就是超越 GCC。
Clang 开发事出有因,Wiki 介绍以下:
Apple 使用 LLVM 在不支持所有 OpenGL 特性的 GPU (Intel 低端显卡) 上生成代码 (JIT),令程序仍然可以正常运行。以后 LLVM 与 GCC 的集成过程引起了一些不快,GCC 系统庞大而笨重,而 Apple 大量使用的 Objective-C 在 GCC 中优先级很低。此外 GCC 做为一个纯粹的编译系统,与 IDE 配合不好。加之许可证方面的要求,Apple 没法使用修改版的 GCC 而闭源。因而 Apple 决定从零开始写 C family 的前端,也就是基于 LLVM 的 Clang 了。
Clang 的特性:
固然,GCC 也有其优点:
通常能够将编程语言分为两种,编译语言和直译式语言。
iOS开发目前的经常使用语言是:Objective和Swift。两者都是编译语言,换句话说都是须要编译才能执行的。两者的编译都是依赖于Clang + LLVM.
无论是OC仍是Swift,都是采用Clang做为编译器前端,LLVM(Low level vritual machine)做为编译器后端。因此简单的编译过程如图
编译器前端的任务是进行:语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程当中,会进行类型检查,若是发现错误或者警告会标注出来在哪一行。
编译器后端会进行机器无关的代码优化,生成机器语言,而且进行机器相关的代码优化。iOS的编译过程,后端的处理以下
当你在XCode中,选择build的时候(快捷键command+B),会执行以下过程
Entitlements:
{
"application-identifier" = "app的bundleid";
"aps-environment" = development;
}
复制代码
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
复制代码
经过这个编译的命令,咱们能够看到
咱们在每次编译事后,都会生成一个dsym文件。dsym文件中,存储了16进制的函数地址映射。
在App实际执行的二进制文件中,是经过地址来调用方法的。在App crash的时候,第三方工具(Fabric,友盟等)会帮咱们抓到崩溃的调用栈,调用栈里会包含crash地址的调用信息。而后,经过dSYM文件,咱们就能够由地址映射到具体的函数位置。
XCode中,选择Window -> Organizer能够看到咱们生成的archier文件
iOS 如何调试第三方统计到的崩溃报告 (http://blog.csdn.net/hello_hwc/article/details/50036323)
或多或少,你都会在第三方库或者iOS的头文件中,见到过attribute。
好比
__attribute__ ((warn_unused_result)) //若是没有使用返回值,编译的时候给出警告
复制代码
attribtue 是一个高级的的编译器指令,它容许开发者指定更更多的编译检查和一些高级的编译期优化。
分为三种:
语法结构
attribute 语法格式为:attribute ((attribute-list))
放在声明分号“;”前面。
好比,在三方库中最多见的,声明一个属性或者方法在当前版本弃用了
@property (strong,nonatomic)CLASSNAME * property __deprecated;
复制代码
这样的好处是:给开发者一个过渡的版本,让开发者知道这个属性被弃用了,应当使用最新的API,可是被__deprecated的属性仍然能够正常使用。若是直接弃用,会致使开发者在更新Pod的时候,代码没法运行了。
__attribtue__的使用场景不少,本文只列举iOS开发中经常使用的几个:
//弃用API,用做API更新
#define __deprecated __attribute__((deprecated))
//带描述信息的弃用
#define __deprecated_msg(_msg) __attribute__((deprecated(_msg)))
//遇到__unavailable的变量/方法,编译器直接抛出Error
#define __unavailable __attribute__((unavailable))
//告诉编译器,即便这个变量/方法 没被使用,也不要抛出警告
#define __unused __attribute__((unused))
//和__unused相反
#define __used __attribute__((used))
//若是不使用方法的返回值,进行警告
#define __result_use_check __attribute__((__warn_unused_result__))
//OC方法在Swift中不可用
#define __swift_unavailable(_msg) __attribute__((__availability__(swift, unavailable, message=_msg)))
复制代码
你必定还见过以下代码:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
///代码
#pragma clang diagnostic pop
复制代码
这段代码的做用是
经过clang diagnostic push/pop,你能够灵活的控制代码块的编译选项。
- iOS 合理利用Clang警告来提升代码质量 (http://blog.csdn.net/Hello_Hwc/article/details/46425503)
所谓预处理,就是在编译以前的处理。预处理可以让你定义编译器变量,实现条件编译。
好比,这样的代码很常见
#ifdef DEBUG
//...
#else
//...
#endif
复制代码
一样,咱们一样也能够定义其余预处理变量,在XCode-选中Target-build settings中,搜索proprecess。而后点击图中蓝色的加号,能够分别为debug和release两种模式设置预处理宏。
好比咱们加上:TestServer,表示在这个宏中的代码运行在测试服务器
而后,配合多个Target(右键Target,选择Duplicate),单独一个Target负责测试服务器。这样咱们就不用每次切换测试服务器都要修改代码了。
#ifdef TESTMODE
//测试服务器相关的代码
#else
//生产服务器相关代码
#endif
复制代码
一般,若是你使用CocoaPod来管理三方库,那么你的Build Phase是这样子的:
其中:[CP]开头的,就是CocoaPod插入的脚本。
而这些配置信息都存储在这个文件(.xcodeprog)里
到这里,CocoaPod的原理也就大体搞清楚了,经过修改xcodeproject,而后配置编译期脚本,来保证三方库可以正确的编译链接。
一样,咱们也能够插入本身的脚本,来作一些额外的事情。好比,每次进行archive的时候,咱们都必须手动调整target的build版本,若是一不当心,就会忘记。这个过程,咱们能够经过插入脚本自动化。
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
复制代码
这段脚本其实很简单,读取当前pist的build版本号,而后对其加一,从新写入。
使用起来也很简单:
而后把这段脚本拷贝进去,而且勾选Run Script Only When installing,保证只有咱们在安装到设备上的时候,才会执行这段脚本。重命名脚本的名字为Auto Increase build number
而后,拖动这个脚本的到Link Binary With Libraries下面
脚本化编译打包对于CI(持续集成)来讲,十分有用。iOS开发中,编译打包必备的两个命令是:
//编译成.app
xcodebuild -workspace $projectName.xcworkspace -scheme $projectName -configuration $buildConfig clean build SYMROOT=$buildAppToDir
//打包
xcrun -sdk iphoneos PackageApplication -v $appDir/$projectName.app -o $appDir/$ipaName.ipa
经过info命令,能够查看到详细的文档
info xcodebuild
复制代码
以前写的一套基于 Python 的编译打包脚本 (https://github.com/ReverseScale/AutoBuildScript/blob/master/autobuild.py)
一般,当项目很大,源代码和三方库引入不少的时候,咱们会发现编译的速度很慢。在了解了XCode的编译过程后,咱们能够从如下角度来优化编译速度:
咱们须要一个途径,可以看到编译的时间,这样才能有个对比,知道咱们的优化究竟有没有效果。
对于XCode 8,关闭XCode,终端输入如下指令
defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
复制代码
而后,重启XCode,而后编译,你会在这里看到编译时间。
2.1)forward declaration
所谓forward declaration,就是@class CLASSNAME,而不是#import CLASSNAME.h。这样,编译器能大大提升#import的替换速度。
2.2)对经常使用的工具类进行打包(Framework/.a)
打包成Framework或者静态库,这样编译的时候这部分代码就不须要从新编译了。
2.3)经常使用头文件放到预编译文件里
XCode的pch文件是预编译文件,这里的内容在执行XCode build以前就已经被预编译,而且引入到每个.m文件里了。
3.1)Debug模式下,不生成dsym文件
上文提到了,dysm文件里存储了调试信息,在Debug模式下,咱们能够借助XCode和LLDB进行调试。因此,不须要生成额外的dsym文件来下降编译速度。
3.2)Debug开启Build Active Architecture Only
在XCode -> Build Settings -> Build Active Architecture Only 改成YES。这样作,能够只编译当前的版本,好比arm7/arm64等等,记得只开启Debug模式。这个选项在高版本的XCode中自动开启了。
3.3)Debug模式下,关闭编译器优化
编译器优化
关于 iOS 编译 Clang LLVM 相关的知识整理参见: 深刻剖析 iOS 编译 Clang LLVM
此系列文章内容多为网上资料整理,文章结尾会列出参照连接,若有纰漏欢迎讨论🤗
以上文章整理自:https://my.oschina.net/u/2345393/blog/820141,https://linuxtoy.org/archives/llvm-and-clang.html,https://blog.csdn.net/hello_hwc/article/details/53557308,https://github.com/ming1016/study/wiki/深刻剖析-iOS-编译-Clang---LLVM