系列文章:OC底层原理系列,OC基础知识系列,Swift底层探索系列,iOS高级进阶系列数组
前段时间,同事给我推荐了一篇美团的文章:一款可让大型iOS工程编译速度提高50%的工具,一看标题就以为惊讶,为何呢?由于它能让编译速度提示50%且不是经过组件二进制化实现
,咱们平常的提高编译速度
就是将组件编译成二进制文件导入项目
。本着不清楚的就去了解的原则,就来看看怎么实现的。xcode
在项目中咱们会引入头文件,例以下图:咱们在ViewController中引入了Person的头文件
在咱们
引入头文件
的时候,引入的是头文件的名称Person
,那么Xcode是怎么找到这个Person文件实际位置的呢?这就要提到项目中配置的header search path
Xcode
在编译
的时候
会读取到header search path的地址
,而且拼接
上咱们引入的头文件名
。markdown
也就意味着咱们导入的头文件
分红两个部分
:数据结构
前半部分
:头文件所在的文件目录
后半部分
:头文件名称
这也就是为何咱们设置header search path的时候,只须要设置头文件所在目录就能够
了。app
问题:由于咱们项目里有不少文件
,那么咱们就会在header search path设置不少目录
,可是对于找到咱们上面引入一个头文件Person
,他须要查找遍历全部
的文件目录
,来找到这个类
。这个过程随着项目的类愈来愈多
,查找
的时间
就会愈来愈长
,就会愈来愈耗时
。好比咱们项目组件多达上百个,类有上万个,那么这个过程所产生的的耗时就比较明显了。函数
上面咱们知道项目编译耗时的缘由,那么怎么解决这个问题呢?美团的文章给出答案,就是使用hmap
工具
hmap是什么呢?美团文章说了它就是Header Map的实体
,相似于一个Key-Value的形式
,Key值
是头文件
的名称
,Value
是头文件
的实际物理路径
,其实这个东西一直都存在
,只不过咱们没注意到罢了。post
第一次
运行项目或者编译的时候,会发现很慢
,可是一旦运行
或者编译成功
后,再次编译
或者运行
就会很快
,想过为何没?第一次编译后
,Xcode
就会
帮咱们生成一些.hmap文件
,再次编译
时候会直接使用
这些.hmap文件快速找到
对应的头文件
,因此编译速度就会快不少咱们看到
生成
了不少.hmap文件
,Xcode
是按类别生成
的,箭头指
的就是咱们主项目工程
的.hmap文件
,若是咱们对Xcode进行清理
,那么这些.hmap文件
也会被清掉
,而后咱们就会发现,编译又慢
了起来。学习
经过上面的讲解咱们知道.hmap
其实就是个容器
,它内部
确定包含
了Person
的文件目录
,那么就会让
咱们Xcode
在查找Person
的头文件
时更快
速,那么有个问题就出来了,咱们本身怎么去生成.hmap文件
呢?.hmap
的底层结构
又是怎样的呢?测试
咱们编译一个项目,查看编译过程,找到ViewController.m文件
我用
红[]括住
,咱们能够看到它是用-I参数
去引入了一个.hmap文件
,上面咱们也知道Xcode
会生成多个.hmap
,为了方便你们理解咱们须要读取下.hmap文件
先看下项目目录 咱们再看下
这个项目生成的.hmap
是什么文件格式
咱们发现这个里面
包含了项目里全部的.h
,下面咱们来看看.hmap
究竟是什么样的数据结构
咱们能够经过LLVM来查找相关的内容
咱们看到有个
结构体叫HMapHeader
,还有个结构体叫HMapBucket
,红框有两句话:1.有一个NumBuckets的HMapBucket对象数组紧跟在这个头文件后面
。2.有个字符串跟随在HMapBucket后面,在StringsOffset
经过上面咱们能够猜想一下.hmap的结构
最上面的HMapHeader,记录一些必要信息
中间的HMapBucket,有多少个头文件,就会有多少个HMapBucket,这些都会包装成HMapBucket
字符串里就是包含着头文件的前半部分路径以及后半部分类名的字符串
流程:经过
读取HMapHeader
,获取.hmap保存了多少个Bucket
,也就知道了这个.hmap保存
了多少
个头文件路径
,而Bucket里保存
了这个头文件在下面字符串中的偏移量
,而后就能够从最下面的字符串
中读取到该头文件的路径
咱们怎么读取.hmap信息呢?上面从LLVM
中咱们找到hmap的有关结构信息
,那么在LLVM里面是否有存取相关内容呢?
结构体信息
是在Lex文件下
找到,那么读取信息是否是也在Lex中HeaderMapTest
的文件
,感受是测试HeaderMap的文件
咱们在
读取hmap时
,须要用到上面的结构体
下面咱们就来用LLVM获取的信息,写一个读取HeaderMap的插件
(咱们在main文件中写)
咱们在main函数中写以下代码:
HMAP_HeaderMagicNumber
是字符串翻转
,由于在HMapHeader结构体
中有个属性Magic来表示字节顺序
,也就是说若是当前的Magic=HMAP_SwappedMagic
,也就意味着字节顺序是反转
的,也就须要从新交换下字节顺序
当参数小于两个的时候
(说明没有传什么东西)这个时候就认为是无效
的
循环经过dump方法导出header map
这个方法我是使用C
来写的,由于感受C在处理取文件时更方便些
传进来的是
文件路径
解析路径
解析
的路径长度小于0
说明路径不正常
获取MapHeader大小并判断
拿到
MapHeader大小
,若是<0
则说明MapHeader异常
,若是小于实际的MapHeader大小
,则说明读取
的数据异常
判断字符串是否翻转,读取header
获取桶的数目
获取桶的数组
(指针偏移)获取String列表
(指针偏移)遍历获取桶
,而后取出桶
的前缀
和后缀进行拼接
上面咱们就把一个
读取.hmap的代码写好
了,下面将以前的项目的.hmap代码放到这个项目目录里,而后在下图进行设置
第一个是
当前可执行文件
的路径
,第二个是刚才配置的.hmap路径
打印是
16个桶
,可是不都是头文件地址
(因为数据对齐
的缘由)
String表有9个数据
,bucket数目有16个
经过上面的读取打印咱们能够确认一下几点:
.hmap是一个key-value形式
,key是头文件名
prefix保存的是头文件路径的前半部分
suffix保存的是头文件路径的后半部分
(头文件名).hmap是按照对应规则存储的一堆头文件
也证实了上面咱们的猜测是对的
上面写的代码能够生成一个工具,咱们把工具添加
到咱们的lldb执行命令里
,这样咱们就不用上面的方式读取.hmap文件,咱们就能够在终端使用命令同样读取
上面说了xcode
本身就能主动
帮咱们生成.hmap文件
,那为何还须要咱们本身写呢?美团的文章里说了,这里我再简单的说下:
经过cocoaPods来管理第三方
,好比我以前没事写的Swift项目引入下面的第三方库以#import "ClassA.h"形式的头文件
,才会命中.hmap文件
,不然都将经过Header Search Path寻找其相关路径
目录的问题上面说过
它会在多个目录里查找一个头文件是比较耗时
,那么若是我把一个文件路径放到一个.hmap文件中
,那就回快不少
。此时若是引入的组件和第三方比较多
,那么势必会致使编译速度慢
。
这部分也是个难点,本人也是查看了上面提到的LLVM中的HeaderMapTest.cpp文件
,仔细看了下代码,发现里面有些生成.hmap代码
,本身写的代码比较的简单
,就是为了说明.hmap是如何生成
的
.hmap文件
说到,里面包含不少的Bucket
,因此咱们要先生成Bucket
建立MapFile容器Maker
,Maker中包含一个个MapFile
,也就是Bucket
,MapFile是一个结构体
(HeaderMapTest.cpp中同样,其中的8表明多少个Bucket
,750是生成buffer的大小
)
类名
和路径以Bucket
的形式保存
上面的方法都是
从LLVM的HeaderMapTest.cpp中找到的
生成了一个
TestApp.hmap文件
,下面咱们来读取
下这个文件,看看和Xcode生成的是否同样
和Xcode生成的.hmap
咱们发现
生成
的结果
是同样
的,下面咱们就去使用下这个本身生成.hmap
将Use Header Maps设置为NO,
将Header Search Paths路径设置成咱们生成的.hmap路径
。因为写的项目工程过小,测不出来太大的差异
。
上面讲了.hmap的读写方法,看完也就.hmap有个比较清晰的认识了,美团文章解决编译速度的思路值得咱们去学习
,我上面生成.hmap
的方法
其实没法落地
的,就是为了给你们说一下怎么去生成一个.hmap
,美团文章里说
的cocoapods-hmap-prebuilt这个插件
,我我的感受是一个脚本
,遍历头文件脚本
。上面说的生成.hmap方法没法落地
,若是让它可以落地
,就是写一个脚本
去遍历项目
以及cocoapods管理
的第三方库
的头文件
,将头文件提取出来
,用上面
的方法
,最后生成
一个.hmap文件
,这样才能落地
。这部分也做为本身的一个技术探索吧,后面有告终果再给你们分享
.hmap已经落地,能够看下篇文章由美团文章“一款可让大型iOS工程编译速度提高50%的工具”引出的.hmap文件(下)hmap落地,文章结尾放出来插件连接