Series of articles:OC Basic principle series.OC Basic knowledge series.Swift bottom exploration series.IOS Advanced advanced series

preface

Some time ago, a colleague recommended to me an article about Meituan: a tool that can improve the compilation speed of large iOS projects by 50%. I was surprised when I saw the title. Why? Because it makes compilation 50% faster and is not achieved through component binarization, our daily speed increase is to compile components into binaries and import them into projects. In line with the principle of what is not clear to understand, let’s see how it is implemented.

explore

Cause of Compilation Time

In the project we will introduce a header file, as shown below: weThe Person header file is introduced in the ViewController In ourImporting header filesWhen you’re talking aboutThe header file is named PersonHow does Xcode find the actual location of the Person file? This is to mentionHeader Search path configured in the project XcodeincompilethewhenwillRead the address of header Search pathAnd,Joining togetherOn ourThe imported header file name.

This means that our imported header is split into two parts:

  • 1.The first half:The directory in which the header file resides
  • 2.In the second part:Header file name

That’s why when you set the header search path, you only need to set the directory where the header file is located.

Problem: Because we have a lot of files in our project, we will set a lot of directories in the header search path, but for finding we introduce a header file named Person, which will need to search through all the file directories to find the class. This process becomes longer and more time-consuming as the project has more classes. For example, if we have hundreds of project components and tens of thousands of classes, the time consuming caused by this process will be obvious.

The solution

We already know why the project takes time to compile, but how can we solve this problem? The answer, according to Meituan’s article, is to use HMAP

hmap

What is HMAP? This is the entity of a Header Map, which is similar to a key-value. The Key Value is the name of the Header file, and the Value is the actual physical path of the Header file. This has always been there, but we didn’t notice it.

  • If you think about it,For the first time,When you run your project or compile it, you’ll noticeslowBut oncerunorCompile successfullyLater,Compile againorrunwillsoonEver wonder why?
  • Actually,After the first compilation.XcodeitwillTo help usGenerate some.hmap files.Compile againWhen willDirect use oftheseThe.hmap file is quickly foundThe correspondingThe header file, so the compilation will be much faster

We’re seeing a lot of.hmap files being generated, Xcode is generated by category, the arrow is our.hmap file for our main project, and if we clean up Xcode, those.hmap files are also going to get cleaned up, and then we’re going to see that the compilation is slow again.

Hmap is a container that contains the Person file directory, which makes it much faster for Xcode to find the Person header file. So how do we generate the.hmap file? What is the underlying structure of hmap?

Explore the.hmap file

We compile a project, look at the compilation process, and find the viewController.m file

I’ll enclose it in red [], so we can see that it uses the -i argument to import a.hmap file, and we know that Xcode generates multiple.hmaps, so we need to read the.hmap file for you to understand

Hmap file structure analysis

Take a look at the project catalogLet’s seeThis project generates.hmapWhat is the file format

We found that this contains all of the.h’s in the project, so let’s see, what is the data structure of the.Hmap

  • The data structure

We can find this through LLVM

We see a structure called HMapHeader and a structure called HMapBucket with two sentences in the red box: 1. This header file is followed by an array of NumBuckets’ HMapBucket objects. 2. There is a string following HMapBucket in StringsOffset

From the above we can guess the structure of.hmap

  • 1.The top HMapHeader, which records the necessary information
  • 2.The HMapBucket in the middle, there are as many hmapBuckets as there are header files, all wrapped as Hmapbuckets
  • 3.The string contains the first half of the header file's path and the last half of the class name

Process: Read the hmap Header and get the number of buckets that hmap holds, the offset of the header in the bottom string, and then read the path of the header from the bottom string

Read the.hmap file

How do we read the.hmap information? LLVM: HMAP: LLVM: hMAP: LLVM: HMAP

  • We know up hereStructural informationisUnder LexIf yes, read information is also in Lex
  • Finally I found oneHeaderMapTestthefile, the feeling isTest the HeaderMap file

We need the above structure when we read hMAP

Let’s use the information obtained by LLVM to write a plug-in that reads HeaderMap (we’ll do it in our main file)

Hmap read

We write the following code in the main function:

  • Assertion macros

HMAP_HeaderMagicNumber is a string reversal, because there is an attribute Magic in the HMapHeader structure that represents byte order, so if Magic=HMAP_SwappedMagic, that means the byte order is reversed, So you need to switch the byte order again

  • 2. Check abnormal files

If the parameter is less than two (meaning nothing is passed), it is considered invalid. Right

  • 3. Normal files

The header map is exported in a loop using the dump method

Dump method

This method I amUse CTo write it, because it feelsC is more convenient in handling fetching files

What’s passed in is the file path

  • 1.Parsing path

If the resolved path length is less than 0, the path is abnormal

  • 2.Get the MapHeader size and determine

If the MapHeader size is smaller than 0, the MapHeader is abnormal. If the MapHeader size is smaller than the actual MapHeader size, the read data is abnormal

  • 3.To check if the string is flipped, read the header

  • 4.Gets the number of buckets

  • 5.Gets an array of buckets(Pointer offset)

  • 6.Getting a list of strings(Pointer offset)

  • 6.Iterate to get bucketsAnd thenRemove the buckettheThe prefixandSplicing suffixes

Let’s just put one up hereRead. Hmap code writtenNow put the. Hmap code of the previous project into the project directory and set it in the image below

Run the project, break point

  • 1. Breakpoint of main function

The first is the path to the current executable, and the second is the.hmap path you just configured

  • 2. Check the number of buckets

Print 16 buckets, but not all header addresses (due to data alignment)

  • 3. View the printed data

The String table has 9 data and 16 bucket numbers

  • 4. View the result

conclusion

By reading and printing above we can confirm several points:

  • 1. It says above. Hmap is a key-value format.Key is the header file name
  • 2.Prefix saves the first half of the header file path
  • 3.The suffix saves the second half of the header file path(Header file name)
  • 4.Hmap is a stack of header files stored according to corresponding rules

It also proves that our conjecture above is correct

extension

The code written above can generate a tool that we putTool to addTo ourLLDB execution commandSo instead of reading the.hmap file the way above, we can read the.hmap file in theThe terminal reads the data using a command

Generate your own.hmap file

It says that Xcode can generate.hmap files for us, so why do we need to write them ourselves? Meituan said in the article, here I say briefly:

  • 1. Our programs generally doManage third parties through cocoaPodsFor example, the Swift project that I didn’t have to write before introduces the following third-party library

  • 1. 2. Smart refrigeratorA header file in the form #import "classa.h"To beHit the. Hmap file.Otherwise, the related Path is searched through Header Search Path

The directory problem mentioned above is that it takes time to find a header file in multiple directories, so if I put a file path into a.hmap file, it will be much faster. At this time, if there are many imported components and third parties, the compilation speed will be slow.

Write code to generate your own.hmap file

This part is also difficult, I also check the above mentioned LLVM headermaptest. CPP file, take a closer look at the code, found that there are some generated. Hmap code, I write the code is simpler, is to explain

  • 1. Introduction aboveThe hmap fileSpeaking of, insideContains many bucketsSo we’re going toMr Into the Bucket

Create a buffer that contains a single MapFile, or Bucket, as in headermaptest. CPP, where 8 is the number of buckets and 750 is the size of the buffer.

  • 2. Core code, willThe name of the classandPath to the Bucketthestored
    • Methods the overview
    • AddString method
    • AddBucket method

The above methods are all found in LLVM’s headerMaptest.cpp

  • 3. Export the file to the specified location
    • Methods the overview
    • GetBuffer method

  • 4. Run the project

A testapp. hmap file is generated. Let’s read this file to see if it is the same as Xcode

  • 5. Read the generated testapp. hmap file

And Xcode generated.hmap

We found that the result is the same, so let’s go ahead and use this to generate.hmap ourselves

  • 6. Use your own. Hmap

Set Use Header Maps to NO and set the Header Search Paths path to the.hmap path that we generated. Because the written items are too small to measure much difference.

conclusion

Hmap () : hmap () : hmap () : hmap () : hmap () : hmap () : hmap (); Cocoapods-map-prebuilt is a script that traverses header files. The.hmap method can’t be implemented. If it can be implemented, you need to write a script that iterates through the header file of your project and any third-party libraries managed by Cocoapods, extract the header file, and then generate a.hmap file using the method above. This part is also a technical exploration of my own, and then share the results with you

supplement

Hmap has been implemented, you can see the next article by Meituan.com article “a tool that can improve compilation speed of large iOS projects by 50%”