What is cocoapoDS-Map-Prebuilt?

Cocoapods – HMAP – PreBuilt is a new cocoapods plug-in developed by Meituanplatform Iteration group. Based on Header Map technology, it further improves the compilation speed of your code and improves the search mechanism for Header files.

While building apps with binary components is the mainstream solution of HPX (Unified Continuous Integration/delivery Platform for Mobile terminals), * * (Profile, but in some scenarios Address/Thread/UB/Coverage Sanitizer, static App level examination, ObjC method call compatibility check, etc.) * *, we build work still needs to complete source code to compile manner; Moreover, in the actual development process, most of them are developed in the way of source code, so we set the experimental object as the process based on full source code compilation.

Without further ado, let’s see how it works in practice!

In general, with meituan.com and Dianping’s full source code compilation process as the experimental subjects, cocoapoDS-Map-Prebuilt plug-in can increase the speed of the total link by more than 45% and the speed of Xcode packaging by more than 50%. To better understand the value and functionality of this plug-in, let’s take a look at some of the problems in the current project.

Why are existing programs not good enough?

Currently, all meituan-based apps do package management on CocoaPods, so during the actual development process, CocoaPods will add a component name directory and a soft link to Header files under Pods/Header/, something like this:

/ Users/sketchk/Desktop/MyApp/Pods └ ─ ─ Headers ├ ─ ─ Private │ └ ─ ─ AFNetworking │ ├ ─ ─ AFHTTPRequestOperation. H - > . / XXX/AFHTTPRequestOperation. H │ ├ ─ ─ AFHTTPRequestOperationManager. H - >. / XXX/AFHTTPRequestOperationManager. H │ ├ ─ ─... │ ├── ch.pdf, ├─ ch.pdf, │ ├─ ch.pdf, │ ├─ ch.pdf, │ ├─ ch.pdf AFHTTPRequestOperation. H - >. / XXX/AFHTTPRequestOperation. H ├ ─ ─ AFHTTPRequestOperationManager. H - > . / XXX/AFHTTPRequestOperationManager. H ├ ─ ─... └ ─ ─ UIRefreshControl + AFNetworking. H - >. / XXX/UIRefreshControl + AFNetworking. HCopy the code

With this directory structure and soft link, CocoaPods is able to add the following parameters to the Header Search Path to make the precompile process go smoothly.

Copy the code

While this way of building Search Path solves the precompile problem, in some projects, such as large projects with up to 400+ components, it can cause the following problems:

  1. Large number of Header Search Path will cause compilation parameters-IOptions inflate so fast that, after reaching a certain length, they even fail to compile
  2. At present, there are nearly 5W header files in the project of Meituan, which means that both the search process of header files and the creation process of soft chain will cause a large number of FILE IO operations, which will lead to some time-consuming operations.
  3. The compilation time will increase sharply with the number of components. Taking The size of Meituan and Dianping, which have more than 400 components, as a reference, the packaging time of the full source code is more than one hour.
  4. There are potential risks to looking for headers based on path order, such as in the case of a header with the same name, and the next header will never participate in the compilation.
  5. Due to the${PODS_ROOT}/Headers/PrivateThe existence of paths makes it possible to reference private header files of other components.

Do you think it would be a headache for engineers to solve the above problem, at best it would take an hour to solve, at worst it would take risky code to go live?

Data collection

What is a Header Map?

Thankfully, cocoapoDS-Map-Prebuilt made these problems a thing of the past, but to understand why it works, we need to understand what a Header Map is.

Header Map is actually a set of Header file information mapping table!

In order to understand the Header Map more intuitively, we can turn on the Use Header Map option in the Build Setting to experience it in real life.

Add the -v parameter to the end of the Build Log to see how it works:

$ clang <list of arguments> -c some-file.m -o some-file.o -v
Copy the code

In the output of console, we find something interesting:

In the figure above, you can see that the compiler shows the order in which to look for header files and the corresponding paths. In these paths, you see something unfamiliar: files with the suffix.hmap followed by a parenthesis that says headerMap.

That’s right! It’s the entity of the Header Map.

Clang has already inserted a mapping table of header names and header paths into the hMAP file mentioned earlier, but it is in binary format. To verify this, we can use the HMAP tool written by Milend to look up the contents.

After running the related command (i.e. Hmap print), we can find that the structure of the information stored in the HMap is similar to a key-value format. The Key Value is the name of the header file, and the Value is the physical path of the header file:

Note that the key content of the mapping table changes depending on the usage scenario, such as header references in “…” Is still <… >

Or how headers are configured in Build Phase. For example, when you set the header file to Public in some HMap

If it is set to project, its Key value may be ClassA, and these letters are configured

The rest place, as shown in the figure below:

Now I think you know what a Header Map is.

Of course, this technique is nothing new, and Facebook’s Buck tool offers something similar, with the file type changed to headerMap.Java.

At this time, I guess you may not be interested in buck, but start to think about what the Public, Private and Project of Headers in the last picture represent. It seems that many students have never paid much attention to it, and why it affects the content in HMAP?

What is Public, Private, Project?

In Apple’s official Xcode Help – What are build Phases? In the document, we can see the following explanation:

Associates public, private, or project header files with the target. Public and private headers define API intended for use by other clients, and are copied into a product for installation. For example, public and private headers in a framework target are copied into Headers and PrivateHeaders subfolders within a product. Project headers define API used and built by a target, but not copied into a product. This phase can be used once per target.

In general, we can know that the Public and Private mentioned in Build Phases-headers refer to the header files that can be used by the outside world, while the header files in Project are not used by the outside world and will not be placed in the final product.

If you continue to read something like StackOverflow-XCode: Copy Headers: Public vs. Private vs. Project? And Stackoverflow-understanding Xcode’s Copy Headers phase, you’ll find that in the early Xcode Help Project Editor section, There is a paragraph called Setting the Role of a Header File that details the differences between the three types.

Public: The interface is finalized and meant to be used by your product’s clients. A public header is included in The product as well readable source code without restriction.

Private: The interface isn’t intended for your clients or it’s in early stages of development. A private header is included in Thus the symbols are visible to all clients, But clients should understand that they’re not supposed to use them.

Project: The interface is for use only by implementation files in the current project. A project header is not included in the target, except in object code. The symbols are not visible to clients at all, only to you. 

So far, we should be able to thoroughly understand the difference between Public, Private and Project. In short, Public is still Public In the usual sense, Private represents the meaning of In Progress, and Project is Private In the usual sense.

The public_header_Files and private_header_Files fields are also included in the Podspec Syntax of CocoaPods. Do they actually have a different meaning to Xcode?

Here’s a closer look at the official documentation explanation, especially the private_Header_Files field.

As you can see, private_header_Files here means that it itself is relative to Public. These header files are not intended to be Public

It is intended to be exposed to the user, and will not produce documentation, but will appear in the final product at build time, only to be neither Public nor

Header files with Private annotations are considered to be truly Private and do not appear in the final product.

It seems that CocoaPods’ official interpretations of Public and Private are the same as those described in Xcode, and the Private ones are not our usual ones

Private is a header file that the developer is Ready to open to the public, but is not fully Ready


Did this one blow your mind a little? So, in the real world, are we using them correctly?

Recommended data

Why can’t native HMAP improve compilation speed?

We’ve explained what HMAP is and how to enable it (enabling the Use Header Map option in the Build Setting), as well as some of the factors (Public, Private, and Project) that affect hMAP generation.

So can I just turn on the Use Header Map provided by Xcode to speed up compilation?

Unfortunately, the answer is no!

As for why, let’s start with the following example. Suppose we have an all-source project built on CocoaPods. Its overall structure is as follows:

  • First, Host and Pod are our two projects, and the product of Target under Pods is Static Library.
  • Second, there will be a Target with the same name under Host, and n+1 targets under Pods, where n depends on the number of components you depend on, and 1 is a Target named Pod-xxx, and finally, The pod-xxx Target product is dependent on the Target in Host.

The entire structure looks like this:

When building a Static Library artifact, CocoaPods creates a header artifact with the following logic:

  • Regardless of how you set public_header_Files and private_header_Files in PodSpec, the corresponding header files will be set to Project.

  • All header files declared as public_header_Files are saved in Pods/Headers/Public.

  • Pods/Headers/Private holds all header files, whether described by public_header_Files or private_header_Files, or those not described. This directory is the complete set of header files for the current component.

  • If Public and Private are not marked in the PodSpec, Pods/Headers/Public and Pods/Headers/Private are the same and contain all Headers.

Because of this mechanism, some interesting questions arise.

  • First, since all Headers are retained as final products, we can refer to Private Headers from other components in conjunction with the Pods/Headers/Private Path in the Header Search Path. For example, I can just make #import

    and it will match the path of the private file.

  • Second, in the case of Static Library, once we have the Use Header Map enabled, the hMap will only contain the #import “classa.h” key reference if all the Header files in the component are of type Project. In other words, only #import “classa.h” will match the hMAP policy. Otherwise, Header Search Path will be used to find the related Path, such as PodB in the following figure. Xcode generates five HMAP files for PodB, which means that these five files will only be used in compiling PodB. PodB will rely on some of PodA’s header files, but since the header files in PodA are of type Project, So all the keys in the HMAP are classa.h, which means we can only import them by #import “classa.h”.

As we know, when referring to other components, it is common to use the #import <A/A.h> method. The reason for this is that it makes it clear where the header is coming from, which avoids problems, and it also allows us to switch between whether to enable the Clang Module or not. The other thing, of course, is that Apple has advised developers at WWDC on more than one occasion to introduce headers in this way.

Following on from the topic above, enabling the Use Header Map option in Static Library does not help us compile faster when we import Header files in the standard way of #import .

But is there really no way to use the Header Map?

Cocoapods hmap – prebuilt was born

Of course, there is always a way to solve this problem. We can make a hMAP file based on CocoaPods rules. It is based on this idea that Meituanself-developed CocoapoDS-HMAP-Prebuilt plug-in was born!

Its core features are few and far between.

  • Using CocodPods to handle Header Search Path and create Header soft link, build the Header index table and generate N +1 HMAP files from it (n is each component’s own Private Header information, 1 is the Public Header information common to all components).

  • Overwrite the Header Search Path in the XCConfig file to the corresponding HMAP file, with one pointing to the private HMap of the component and one pointing to the public HMap shared by all components.

  • Only key-values in the form of component names or header names are allowed to be saved to check for abnormal behaviors caused by duplicate header files in the public Hmap.

  • Disable the Ues Header Map function of the component to reduce unnecessary file creation and reading.

This may sound a little convoluted and a lot of content, but you don’t have to worry about it, you just need to follow these two steps to put it to use:

  1. Declare the plugin in Gemfile.

  2. Use plugins in podfiles.

    // this is part of Gemfile source ‘sakgems.sankuai.com/’ do gem ‘cocoapods-hmap-prebuilt’ gem ‘XXX’ … end

    // this is part of Podfile target ‘XXX’ do plugin ‘cocoapods-hmap-prebuilt’ pod ‘XXX’ … end

In addition, to extend its usefulness, we also provide header patch (to address the directional selection of header files with the same name) and environment variable injection (non-invasive for use in other systems) to facilitate its use in different scenarios.


This is the end of cocoapoDS-Map-Prebuilt.

Going back to the beginning of the story, the Header Map is a small knowledge I found while researching the Swift/Objective-C mix, and Xcode itself implements a set of features based on the Header Map, which does not perform very well in practice.

Fortunately, in subsequent explorations, we found out why Xcode’s Header Map didn’t work and why it was incompatible with CocoaPods. Although it’s not complicated, the core point of Xcode is to convert IO operations such as finding and reading files into memory operations. But combined with the actual business scenario, we found that its revenue is very considerable.

Perhaps it’s a reminder that we should always be curious about technology!

It’s possible to use the Clang Module technology to solve some of the problems mentioned at the beginning of this article, but that’s not the scope of this article. If you’re interested in the Clang Module or mixing Swift with Objective-C, Check out the reference documentation for more details on understanding Swift and Objective-C and blending mechanisms from a Precompiled Perspective.

Reference documentation

  • Apple – WWDC 2018 Behind the Scenes of the Xcode Build Process
  • Apple headerMap. CPP source code
  • Meituan Institute of Technology – Understanding Swift and Objective-C and blending mechanisms from a precompile perspective