由美团文章“一款可让大型iOS工程编译速度提高50%的工具”引出的.hmap文件探索(上)

系列文章:OC底层原理系列OC基础知识系列Swift底层探索系列iOS高级进阶系列数组

前言

前段时间,同事给我推荐了一篇美团的文章:一款可让大型iOS工程编译速度提高50%的工具,一看标题就以为惊讶,为何呢?由于它能让编译速度提示50%且不是经过组件二进制化实现,咱们平常的提高编译速度就是将组件编译成二进制文件导入项目。本着不清楚的就去了解的原则,就来看看怎么实现的。xcode

探索

编译耗时缘由

在项目中咱们会引入头文件,例以下图:咱们在ViewController中引入了Person的头文件 image.png 在咱们引入头文件的时候,引入的是头文件的名称Person,那么Xcode是怎么找到这个Person文件实际位置的呢?这就要提到项目中配置的header search path image.png Xcode编译时候读取到header search path的地址,而且拼接上咱们引入的头文件名markdown

也就意味着咱们导入的头文件分红两个部分数据结构

  • 1.前半部分头文件所在的文件目录
  • 2.后半部分头文件名称

这也就是为何咱们设置header search path的时候,只须要设置头文件所在目录就能够了。app

问题:由于咱们项目里有不少文件,那么咱们就会在header search path设置不少目录,可是对于找到咱们上面引入一个头文件Person,他须要查找遍历全部文件目录,来找到这个类。这个过程随着项目的类愈来愈多查找时间就会愈来愈长,就会愈来愈耗时。好比咱们项目组件多达上百个,类有上万个,那么这个过程所产生的的耗时就比较明显了。函数

解决办法

上面咱们知道项目编译耗时的缘由,那么怎么解决这个问题呢?美团的文章给出答案,就是使用hmap工具

hmap

hmap是什么呢?美团文章说了它就是Header Map的实体,相似于一个Key-Value的形式Key值头文件名称Value头文件实际物理路径,其实这个东西一直都存在,只不过咱们没注意到罢了。post

  • 你们想一下,第一次运行项目或者编译的时候,会发现很慢,可是一旦运行或者编译成功后,再次编译或者运行就会很快,想过为何没?
  • 其实第一次编译后Xcode帮咱们生成一些.hmap文件再次编译时候会直接使用这些.hmap文件快速找到对应的头文件,因此编译速度就会快不少

image.png

咱们看到生成不少.hmap文件Xcode按类别生成的,箭头指的就是咱们主项目工程.hmap文件,若是咱们对Xcode进行清理,那么这些.hmap文件也会被清掉,而后咱们就会发现,编译又慢了起来。学习

经过上面的讲解咱们知道.hmap其实就是个容器,它内部确定包含Person文件目录,那么就会咱们Xcode查找Person头文件更快速,那么有个问题就出来了,咱们本身怎么去生成.hmap文件呢?.hmap底层结构又是怎样的呢?测试

探究.hmap文件

咱们编译一个项目,查看编译过程,找到ViewController.m文件 image.png

我用红[]括住,咱们能够看到它是用-I参数引入了一个.hmap文件,上面咱们也知道Xcode生成多个.hmap,为了方便你们理解咱们须要读取下.hmap文件

.hmap文件结构分析

先看下项目目录 image.png 咱们再看下这个项目生成的.hmap是什么文件格式 image.png

咱们发现这个里面包含了项目里全部的.h,下面咱们来看看.hmap究竟是什么样的数据结构

  • 数据结构

咱们能够经过LLVM来查找相关的内容 image.png

咱们看到有个结构体叫HMapHeader,还有个结构体叫HMapBucket,红框有两句话:1.有一个NumBuckets的HMapBucket对象数组紧跟在这个头文件后面。2.有个字符串跟随在HMapBucket后面,在StringsOffset

经过上面咱们能够猜想一下.hmap的结构 image.png

  • 1.最上面的HMapHeader,记录一些必要信息
  • 2.中间的HMapBucket,有多少个头文件,就会有多少个HMapBucket,这些都会包装成HMapBucket
  • 3.字符串里就是包含着头文件的前半部分路径以及后半部分类名的字符串

流程:经过读取HMapHeader获取.hmap保存了多少个Bucket,也就知道了这个.hmap保存多少头文件路径,而Bucket里保存了这个头文件在下面字符串中的偏移量,而后就能够从最下面的字符串读取到该头文件的路径

读取.hmap文件

咱们怎么读取.hmap信息呢?上面从LLVM中咱们找到hmap的有关结构信息,那么在LLVM里面是否有存取相关内容呢?

  • 上面咱们知道结构体信息在Lex文件下找到,那么读取信息是否是也在Lex中
  • 最后我找到一个HeaderMapTest文件,感受是测试HeaderMap的文件

image.png

咱们在读取hmap时,须要用到上面的结构体

下面咱们就来用LLVM获取的信息,写一个读取HeaderMap的插件(咱们在main文件中写)

hmap读取

咱们在main函数中写以下代码:

  • 断言宏

image.png

HMAP_HeaderMagicNumber字符串翻转,由于在HMapHeader结构体有个属性Magic来表示字节顺序,也就是说若是当前的Magic=HMAP_SwappedMagic,也就意味着字节顺序是反转的,也就须要从新交换下字节顺序

  • 2.参数判断非正常文件

image.png

当参数小于两个的时候(说明没有传什么东西)这个时候就认为是无效

  • 3.正常文件

image.png

循环经过dump方法导出header map

dump方法

这个方法我是使用C来写的,由于感受C在处理取文件时更方便些 image.png

传进来的是文件路径

  • 1.解析路径

image.png

解析路径长度小于0说明路径不正常

  • 2.获取MapHeader大小并判断

image.png

拿到MapHeader大小,若是<0说明MapHeader异常,若是小于实际的MapHeader大小,则说明读取数据异常

  • 3.判断字符串是否翻转,读取header

image.png

  • 4.获取桶的数目

image.png

  • 5.获取桶的数组(指针偏移)

image.png

  • 6.获取String列表(指针偏移)

image.png

  • 6.遍历获取桶,而后取出桶前缀后缀进行拼接

image.png 上面咱们就把一个读取.hmap的代码写好了,下面将以前的项目的.hmap代码放到这个项目目录里,而后在下图进行设置 image.png

运行项目,打断点

  • 1.main函数断点

image.png

第一个是当前可执行文件路径,第二个是刚才配置的.hmap路径

  • 2.查看桶数目

image.png

打印是16个桶,可是不都是头文件地址(因为数据对齐的缘由)

  • 3.查看打印数据

image.png

String表有9个数据bucket数目有16个

  • 4.查看结果

image.png

总结

经过上面的读取打印咱们能够确认一下几点:

  • 1.上面说的.hmap是一个key-value形式key是头文件名
  • 2.prefix保存的是头文件路径的前半部分
  • 3.suffix保存的是头文件路径的后半部分(头文件名)
  • 4..hmap是按照对应规则存储的一堆头文件

也证实了上面咱们的猜测是对的

扩展

上面写的代码能够生成一个工具,咱们把工具添加到咱们的lldb执行命令里,这样咱们就不用上面的方式读取.hmap文件,咱们就能够在终端使用命令同样读取 image.png

生成本身的.hmap文件

上面说了xcode本身就能主动帮咱们生成.hmap文件,那为何还须要咱们本身写呢?美团的文章里说了,这里我再简单的说下:

  • 1.咱们的项目通常都会经过cocoaPods来管理第三方,好比我以前没事写的Swift项目引入下面的第三方库

image.png

  • 2.上面咱们发现以#import "ClassA.h"形式的头文件,才会命中.hmap文件不然都将经过Header Search Path寻找其相关路径

image.png

目录的问题上面说过它会在多个目录里查找一个头文件是比较耗时,那么若是我把一个文件路径放到一个.hmap文件中,那就回快不少。此时若是引入的组件和第三方比较多,那么势必会致使编译速度慢

写代码生成本身的.hmap文件

这部分也是个难点,本人也是查看了上面提到的LLVM中的HeaderMapTest.cpp文件,仔细看了下代码,发现里面有些生成.hmap代码,本身写的代码比较的简单,就是为了说明.hmap是如何生成

  • 1.上面介绍.hmap文件说到,里面包含不少的Bucket,因此咱们要先生成Bucket

image.png

建立MapFile容器Maker,Maker中包含一个个MapFile,也就是BucketMapFile是一个结构体(HeaderMapTest.cpp中同样,其中的8表明多少个Bucket750是生成buffer的大小

  • 2.核心代码,将类名路径以Bucket形式保存
    • 方法总览 image.png
    • addString方法 image.png
    • addBucket方法
      image.png

上面的方法都是从LLVM的HeaderMapTest.cpp中找到的

  • 3.将文件导出指定位置
    • 方法总览 image.png
    • getBuffer方法
      image.png
  • 4.运行项目

image.png

生成了一个TestApp.hmap文件,下面咱们来读取下这个文件,看看和Xcode生成的是否同样

  • 5.读取生成的TestApp.hmap

image.png 和Xcode生成的.hmap image.png

咱们发现生成结果同样的,下面咱们就去使用下这个本身生成.hmap

  • 6.使用本身生成.hmap

image.png

将Use Header Maps设置为NO,将Header Search Paths路径设置成咱们生成的.hmap路径。因为写的项目工程过小,测不出来太大的差异

总结

上面讲了.hmap的读写方法,看完也就.hmap有个比较清晰的认识了,美团文章解决编译速度的思路值得咱们去学习,我上面生成.hmap方法其实没法落地的,就是为了给你们说一下怎么去生成一个.hmap美团文章里说cocoapods-hmap-prebuilt这个插件,我我的感受是一个脚本遍历头文件脚本。上面说的生成.hmap方法没法落地,若是让它可以落地,就是写一个脚本遍历项目以及cocoapods管理第三方库头文件将头文件提取出来用上面方法,最后生成一个.hmap文件,这样才能落地。这部分也做为本身的一个技术探索吧,后面有告终果再给你们分享

补充

.hmap已经落地,能够看下篇文章由美团文章“一款可让大型iOS工程编译速度提高50%的工具”引出的.hmap文件(下)hmap落地,文章结尾放出来插件连接

相关文章
相关标签/搜索