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 files
When you’re talking aboutThe header file is named Person
How does Xcode find the actual location of the Person file? This is to mentionHeader Search path configured in the project
Xcode
incompile
thewhen
willRead the address of header Search path
And,Joining together
On 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 noticeslow
But oncerun
orCompile successfully
Later,Compile again
orrun
willsoon
Ever wonder why? - Actually,
After the first compilation
.Xcode
itwill
To help usGenerate some.hmap files
.Compile again
When willDirect use of
theseThe.hmap file is quickly found
The 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.hmap
What 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 here
Structural information
isUnder Lex
If yes, read information is also in Lex - Finally I found one
HeaderMapTest
thefile
, 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 C
To 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 buckets
And thenRemove the bucket
theThe prefix
andSplicing suffixes
Let’s just put one up hereRead. Hmap code written
Now 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 add
To ourLLDB execution command
So 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 do
Manage third parties through cocoaPods
For example, the Swift project that I didn’t have to write before introduces the following third-party library
- 1. 2. Smart refrigerator
A 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 above
The hmap file
Speaking of, insideContains many buckets
So 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, will
The name of the class
andPath to the Bucket
thestored
- 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%”