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

胸怀万里世界, 放眼无限未来。这篇文章主要讲述由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)相关的知识,希望能为你提供帮助。

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

文章图片

前言前段时间,本鑫看了一篇美团的文章:《一款可以让大型ios工程编译速度提升50%的工具》,一看标题就觉得惊讶,为什么呢?因为它能让编译速度提示50%且不是通过组件二进制化实现,我们日常的提升编译速度就是将组件编译成二进制文件导入项目。本着不清楚的就去了解的原则,就来看看怎么实现的。
探索 编译耗时原因在项目中我们会引入头文件,例如下图:我们在ViewController中引入了Person的头文件
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

在我们引入头文件的时候,引入的是头文件的名称Person,那么Xcode是怎么找到这个Person文件实际位置的呢?这就要提到项目中配置的header search path
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

Xcode编译时候读取到header search path的地址,并且拼接上我们引入的头文件名
也就意味着我们导入的头文件分成两个部分
  • 1.前半部分头文件所在的文件目录
  • 2.后半部分头文件名称
这也就是为什么我们设置header search path的时候,只需要设置头文件所在目录就可以了。
问题:因为我们项目里有很多文件,那么我们就会在header search path设置很多目录,但是对于找到我们上面引入一个头文件Person,他需要查找遍历所有文件目录,来找到这个类。这个过程随着项目的类越来越多查找时间就会越来越长,就会越来越耗时。比如我们项目组件多达上百个,类有上万个,那么这个过程所产生的的耗时就比较明显了。
解决办法上面我们知道项目编译耗时的原因,那么怎么解决这个问题呢?美团的文章给出答案,就是使用hmap
hmap
hmap是什么呢?美团文章说了它就是Header Map的实体,类似于一个Key-Value的形式Key值头文件名称Value头文件实际物理路径,其实这个东西一直都存在,只不过我们没注意到罢了。
  • 大家想一下,第一次运行项目或者编译的时候,会发现很慢,但是一旦运行或者编译成功后,再次编译或者运行就会很快,想过为什么没?
  • 其实第一次编译后Xcode帮我们生成一些.hmap文件再次编译时候会直接使用这些.hmap文件快速找到对应的头文件,所以编译速度就会快很多
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

通过上面的讲解我们知道.hmap其实就是个容器,它内部肯定包含Person文件目录,那么就会我们Xcode查找Person头文件更快速,那么有个问题就出来了,我们自己怎么去生成.hmap文件呢?.hmap底层结构又是怎样的呢?
探究.hmap文件我们编译一个项目,查看编译过程,找到ViewController.m文件
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

.hmap文件结构分析
先看下项目目录
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

我们再看下这个项目生成的.hmap是什么文件格式
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 数据结构
我们可以通过LLVM来查找相关的内容
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

通过上面我们可以猜测一下.hmap的结构
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 1.最上面的HMapHeader,记录一些必要信息
  • 2.中间的HMapBucket,有多少个头文件,就会有多少个HMapBucket,这些都会包装成HMapBucket
  • 3.字符串里就是包含着头文件的前半部分路径以及后半部分类名的字符串
读取.hmap文件
我们怎么读取.hmap信息呢?上面从LLVM中我们找到hmap的有关结构信息,那么在LLVM里面是否有存取相关内容呢?
  • 上面我们知道结构体信息在Lex文件下找到,那么读取信息是不是也在Lex中
  • 最后我找到一个HeaderMapTest文件,感觉是测试HeaderMap的文件
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

下面我们就来用LLVM获取的信息,写一个读取HeaderMap的插件(我们在main文件中写)
hmap读取我们在main函数中写如下代码:
  • 断言宏
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 2.参数判断非正常文件
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

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

文章图片

dump方法这个方法我是使用C来写的,因为感觉C在处理取文件时更方便些
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 1.解析路径
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 2.获取MapHeader大小并判断
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 3.判断字符串是否翻转,读取header
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 4.获取桶的数目
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 5.获取桶的数组(指针偏移)
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 6.获取String列表(指针偏移)
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 7.遍历获取桶,然后取出桶前缀后缀进行拼接
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

上面我们就把一个读取.hmap的代码写好了,下面将之前的项目的.hmap代码放到这个项目目录里,然后在下图进行设置
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

运行项目,打断点
  • 1.main函数断点
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 2.查看桶数目
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 3.查看打印数据
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 4.查看结果
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

总结通过上面的读取打印我们可以确认一下几点:
  • 1.上面说的.hmap是一个key-value形式key是头文件名
  • 2.prefix保存的是头文件路径的前半部分
  • 3.suffix保存的是头文件路径的后半部分(头文件名)
  • 4..hmap是按照对应规则存储的一堆头文件
也证明了上面我们的猜想是对的
扩展上面写的代码可以生成一个工具,我们把工具添加到我们的lldb执行命令里,这样我们就不用上面的方式读取.hmap文件,我们就可以在终端使用命令一样读取
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

生成自己的.hmap文件上面说了xcode自己就能主动帮我们生成.hmap文件,那为什么还需要我们自己写呢?美团的文章里说了,这里我再简单的说下:
  • 1.我们的项目一般都会通过cocoaPods来管理第三方,比如我之前没事写的Swift项目引入下面的第三方库
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 2.上面我们发现以#import "ClassA.h"形式的头文件,才会命中.hmap文件否则都将通过Header Search Path寻找其相关路径
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

写代码生成自己的.hmap文件
这部分也是个难点,本人也是查看了上面提到的LLVM中的HeaderMapTest.cpp文件,仔细看了下代码,发现里面有些生成.hmap代码,自己写的代码比较的简单,就是为了说明.hmap是如何生成
  • 1.上面介绍.hmap文件说到,里面包含很多的Bucket,所以我们要先生成Bucket
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 2.核心代码,将类名路径以Bucket形式保存
    • 方法总览
      由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

      文章图片

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

      文章图片

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

      文章图片
  • 3.将文件导出指定位置
    • 方法总览
      由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

      文章图片

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

      文章图片
  • 4.运行项目
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

  • 5.读取生成的TestApp.hmap
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

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

文章图片

  • 6.使用自己生成.hmap
由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)

文章图片

总结上面讲了.hmap的读写方法,看完也就.hmap有个比较清晰的认识了,美团文章解决编译速度的思路值得我们去学习,我上面生成.hmap方法其实无法落地的,就是为了给大家说一下怎么去生成一个.hmap美团文章里说cocoapods-hmap-prebuilt这个插件,我个人感觉是一个脚本遍历头文件脚本。上面说的生成.hmap方法无法落地,如果让它能够落地,就是写一个脚本遍历项目以及cocoapods管理第三方库头文件将头文件提取出来用上面方法,最后生成一个.hmap文件,这样才能落地。这部分也作为自己的一个技术探索吧,后面有了结果再给大家分享
补充【由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件探索(上)】.hmap已经落地,可以看下篇更新的文章《由美团文章“一款可以让大型iOS工程编译速度提升50%的工具”引出的.hmap文件(下)hmap落地》,文章结尾会放出来插件链接

    推荐阅读