preface

How did we optimize compile time by 35% when we understood Swift and Objective-C and its mix mechanism from a precompile perspective? , hands-on practice, record some problems in the process of practice.

review

According to the two articles and my own understanding of a simple comb.

The target

1. Modulize the project; 2. Optimize compilation speed with HMAP

code

The code address



If you are using native Ruby libraries, make sure you have Bundler installed

The module is changed

Module of the project is relatively simple, first enable module function. As mentioned, you can add use_modular_headers! To your Podfile. Module all pods; And modify the DEFINES_MODULE configuration in xcConfig in.podSpec corresponding to each pod, such as S.cconfig = {‘DEFINES_MODULE’ => ‘YES’}. This approach requires modifying the PodSpec file for each pod, which is a bit of a hassle.

I’m using use_frameworks here! :linkage => :static Typing the POD library into framework automatically opens module.

Then I will module the POD library and the main project. Basically, I will encounter some problems about the introduction of unstandardized header files and the navigation macros. I will not mention them again.

Here are my compile-time tests on the project.

switch The first The 2nd The third time 4 times
Open the Module 762s 661s 633s 632s
Close the Module 586s 498s 419s 552s

Compiling on this machine, data fluctuation is relatively large, but it can be seen that the opening of Module does affect the compilation speed.

The system generates Hmap

I did some tests to see how the system-generated Hmap hit at compile time.

The test engineering structure is generally shown in figure. MyMainAPP depends on MyPodC and MyPodC depends on SDWebImage. Introduce #import “mypodcView.h” #import

Introduce #import into MyPodC’s MyPodCView “SDImageCache.h” #import

Watch viewController. m and mypodcView. m compile Header Search Path

When MyMainAPP introduces MyPodC as a.a static library

Compile the ViewController. M

The summary is as follows:

#include “…” search starts here:

MyMainAPP – generated – files. Hmap empty

Mymainapp-project-headers. Hmap only has its own, not matched

#include <… > search starts here:

MyMainAPP – on – target – headers. Hmap empty

MyMainAPP – all – target – headers. Hmap empty

Compile MyPodCView. M

Summary: #include “…” Search starts here: mypodc-generated -files.hmap empty myPODc-project-headers. “> < div style =” box-sizing: border-box; color: RGB (51, 51, 51)

When MyMainAPP is introduced into MyPodC as a framework static library

Compile the ViewController. M

Summary: #include “…” Search starts here: mymainapp-generated files. Hmap empty mymainapp-project-headers. Hmap: empty mymainapp-all-non-framework -target-headers. Hmap: empty mymainapp-all-non-framework -target-headers

Compile MyPodCView. M

The summary is as follows:

#include “…” search starts here:

MyPodC – generated – files. Hmap empty

Mypodc-project-headers. Hmap only has its own, not hit

#include <… > search starts here:

Mypodc-own-target-headers. Hmap only has its own, not matched

MyPodC – all – non – framework – target – headers. Hmap empty

This is me starting Install! A test result of ‘cocoapods’, generate_multiple_pod_projects: true. When generate_multiple_pod_projects is turned on, each Pod generates a project separately. If this switch is turned off, the generated HMap will also be different. But basically, you can see that the Hmap generated by Xcode is not working, and there is almost no hit.

Generate your own Hmap for optimization

The article “How we Optimized compile time by 35% when Swift mixed with Objective-C” details the general functionality of the Hmap plug-in. Avoid reinventing the wheel and see if one is available first.



After a quick look at several libraries, the Cocoapods-MAP-Prebuilt library fits the bill.

The principle of the plugin is very simple, is the hook pod install/update operation, to generate a Hmap for each POD library. Then modify the Header Search Path in the POD library and xcconfig of the main project to keep only the hmap Path.

Here is a principle from below:

  1. HooksManager registers the Post_install hook from Cocoapods.
  2. Run header_mappingS_by_file_accessor to traverse all header files and header_dir with header_dir/header.h and header.h as keys and value as the search path. Assemble a Hash

    to generate A JSON file of all component POD header files, and then use the Hmap tool to convert the JSON file into an Hmap file.
    ,value>
  3. Then modify the HEADER_SEARCH_PATHS value of the. Xcconfig file in each POD to point only to the generated Hmap file and delete the original search directory.
  4. Change the USE_HEADERMAP value of each pod to disable access to the default Hmap file.

MyMainAPP’s xcconfig is as follows: HEADER_SEARCH_PATHS = ${PODS_ROOT}/Headers/HMap/ PODs-myMainApp-prebuilt. HEADER_SEARCH_PATHS = ${PODS_ROOT}/Headers/HMap/MyPodA-prebuilt.hmap ${PODS_ROOT}/Headers/HMap/Pods-MyMainAPP-prebuilt.hmap

Pods-mymainapp-prebuilt. Hmap contains path key pairs for “view. h” and “< pod/view. h>” of all pod libraries. Mypoda-prebuilt. Hmap contains path key pairs from the library such as “mypoda. h”, “<MyPodA/ mypodaview.h >”.

I made a few minor changes: 1. Hmap tool path. To use this plug-in, you need to install the Hmap tool first, but I never did, so I used the local path.

2. Some third-party library names are inconsistent with module names.

3. Delete the PODS_CONFIGURATION_BUILD_DIR path



The PODS_CONFIGURATION_BUILD_DIR path starts with the path of the Pod library framework, which is already hit in HMAP. The system Header file Path is at the bottom of the Header Search Path. Removing the PODS_CONFIGURATION_BUILD_DIR Path will make the system Header file Search faster.

The test results

This is a test result in my project 578s clang Module 511s Clang Module + hmap 673s Clang Module 602s Clang Module + hmap 522s open Clang Module + hmap + removes residual PODS_CONFIGURATION_BUILD_DIR

The data is very volatile and broadly in line with expectations. What kind of expectations? Enabling the Clang Module slows down compilation, and using Hmap optimizes header search, which in turn optimizes compilation.

Is that the end of it?

At this time, I fell into a misunderstanding. I always thought that Clang Module and Hmap were compatible, that is, when Clang Module was enabled, the import of POD library headers would change from a simple copy of header content to a module import, and Hmap optimized the speed of searching for headers when Module import was enabled.

This understanding is wrong ❌.

The main project MyMainAPP relies on the Pod library SDWebImage. The Clang Module function has been enabled and hMap has been generated

And introduce it in the ViewController. According to the clang of the module function, the compiler should automatically # import < SDWebImage/SDAnimatedImagePlayer. H > into @ import. SDWebImage SDAnimatedImagePlayer;

In fact, looking at the precompiled results will revert back to copying (note that the project needs to be clean and clean the MODULE’s PCM file cache directory)

Instead of manually @ import SDWebImage. SDAnimatedImagePlayer; , but compiling error.

Take a look at the Header Search Path when compiling viewController.m

Print the Hmap

Recalling the Clang Module compilation process, I understand it as follows:

When the compiler when compiling ViewController. M, encounter # import < SDWebImage/SDAnimatedImagePlayer. H >, will go to query the Header Search Path, the results of the first hit the hmap Path, The hmap tells the compiler header files in my project directory (MyMainAPP/Pods/SDWebImage/SDWebImage/Core/SDAnimatedImagePlayer. H), He will then go to the Modules directory and look for the ModuleMap file. The result was not found and the Module cache was not hit, so the compiler decided to copy it.

At this point, delete the hmap Path in Header Search Path and clang Module will compile normally again.

This is because the sdWebImage. framework directory in the build directory in the Header Search Path was hit at compile time.

Thinking, I have two direction: 1. The modulemap files and umbrella. H file in MyMainAPP/Pods/SDWebImage/SDWebImage/Core/directory, if the compiler can be found. It didn’t work after trying. 2. If I can generate the compiled path into hmap, I can solve the problem. Such as Xcode/DerivedData MyMainAPP hapsxqbzqwmfppcbsypzshyvkwtw/Build/Products/Debug – iphoneos/SDWebImage/SDWebImage framework / Headers/SDAnimatedImagePlayer. H. However, the environment variable PODS_CONFIGURATION_BUILD_DIR needs to be available at compile time. Insert a script to each Target’s Build Phases, retrieve the compiled directory, and regenerate the hMAP.

However, there is no mention in their article of the need to generate hmaps in this way. There was no good solution until I went back and read the article again and again and saw this sentence

As the Swift/OC mixed programming needs the support of moduleization, meanwhile, the HeaderMap scheme in the industry is used for reference so that OC can avoid the use of moduleization when calling OC

What? To avoid? It turns out I was wrong.

The last

The Clang Module can make OC and Swift calls to each other more convenient and elegant, but can cause long compilation times. With Hmap, you can turn header import back into text import. But hashing up the header path also speeds up compilation.

Personal technical level is limited, are their own blind pondering, if there is a mistake, please correct.

Finally, thanks for your technical article.