当前位置:网站首页>Meituan's good article: understand swift, Objective-C and the mixing mechanism from the perspective of precompiling
Meituan's good article: understand swift, Objective-C and the mixing mechanism from the perspective of precompiling
2022-06-23 05:00:00 【ReyZhang】
Write it at the front
This paper covers a wide range , Length is longer than the , It takes time and energy to finish reading , If you have a clear reading purpose , You can refer to the following suggestions to complete the reading :
- If you already know the theory of precompiling , Can be directly from 【` So it's like this 】 I'm going to start reading the chapter , This will give you a more intuitive understanding of precompiling .
- If you are right about
Search PathI'm interested in , Can be directly from 【 On the first question 】 Reading the chapters of , It's going to make you deeper , A more comprehensive understanding of their operation mechanism , - If you are right about Xcode
PhasesInsideHeaderI'm confused by the settings of , Can be directly from 【 uncover Public、Private、Project The true face of 】 I'm going to start reading , It makes you understand why you say Private It's not really a private header file - If you want to know how to get through
hmapTechnology improves compilation speed , It can be downloaded from 【 be based on hmap Optimize Search Path The strategy of 】 I'm going to start reading , This will give you a new way to speed up compilation . - If you want to know how to get through
VFSTechniques for Swift Product construction , It can be downloaded from 【 On the second question 】 Chapter begins to read , This will allow you to understand how to build with another kind of ascension Swift The efficiency of the product . - If you want to know Swift and Objective-C How to find the method statement , It can be downloaded from 【Swift coming 】 Reading the chapters of , This will enable you to understand the core idea and solution of blending in principle .
summary
With Swift The development of , There are some questions about how to realize in domestic technology community Swift And Objective-C Mixed articles , The main content of these articles is to guide developers to carry out various operations to achieve the effect of mixed editing , For example, in Build Setting Turn on an option in , stay podspec Add a field in , Few articles analyze the working mechanism behind these operations , Most of the core concepts are also brought in one stroke .
Because of this situation , Many developers are faced with unexpected behavior , Or when there are all kinds of strange errors , Will have no way to start , This is also due to the lack of understanding of its working principle .
The author is responsible for CI/CD Related work , It also includes Objective-C And Swift Mixed content , For the purpose of enabling more developers to further understand the mixing mechanism , Wrote this technical article .
I don't say much nonsense , Let's start !
Precompile knowledge guide
#import The mechanism and the shortcomings of
When we use certain system components , We usually write the following form of code :
#import <UIKit/UIKit.h>
#import It's actually #include A small innovation in grammar , They are very close in nature .#include What you do is simple Copy and paste , The target .h The contents of the file are copied to the current file , and Replace This sentence #include, and #import In essence, what we do and #include It's the same , But it also has one more that can Avoid repeated references to header files The power of .
In order to better understand the content behind , We need to talk about how it works ?
From the most intuitive point of view :
Suppose that MyApp.m In file , We #import 了 iAd.h file , After the compiler parses this file , Start looking for iAd Included content (ADInterstitialAd.h,ADBannerView.h), And the sub contents of these contents (UIKit.h,UIController.h,UIView.h,UIResponder.h), And recursion goes on , Last , You'll find that #import <iAd/iAd.h> This piece of code becomes a different SDK The header file of depends on .

If you think it sounds a bit hard , Or I don't understand , We can give a more detailed example here , But remember , about C Preprocessor of language for ,#import It's a special kind of copy and paste .
Combined with the contents mentioned above , stay AppDelegate Add iAd.h:
#import <iAd/iAd.h>
@implementation AppDelegate
//...
@end
Then the compiler will start looking for iAd/iAd.h Which file is it and what content it contains , Let's say it's as follows :
/* iAd/iAd.h */
#import <iAd/ADBannerView.h>
#import <iAd/ADBannerView_Deprecated.h>
#import <iAd/ADInterstitialAd.h>
After finding the above , The compiler copies and pastes it into AppDelegate in :
#import <iAd/ADBannerView.h>
#import <iAd/ADBannerView_Deprecated.h>
#import <iAd/ADInterstitialAd.h>
@implementation AppDelegate
//...
@end
Now? , The compiler found... In the file 3 individual #import sentence 了 , Then we need to continue to look for these files and their corresponding contents , hypothesis ADBannerView.h Is as follows :
/* iAd/ADBannerView.h */
@interface ADBannerView : UIView
@property (nonatomic, readonly) ADAdType adType;
- (id)initWithAdType:(ADAdType)type
/* ... */
@end
Then the compiler will continue to copy and paste its contents into AppDelegate in , It turns out to be like this :
@interface ADBannerView : UIView
@property (nonatomic, readonly) ADAdType adType;
- (id)initWithAdType:(ADAdType)type
/* ... */
@end
#import <iAd/ADBannerView_Deprecated.h>
#import <iAd/ADInterstitialAd.h>
@implementation AppDelegate
//...
@end
Such an operation will Continue To all... In the entire file #import The content pointed to is Replace , It also means that .m The file will eventually become Extremely lengthy .
Although this mechanism seems feasible , But it has two obvious problems : Robustness and expansibility .
Robustness,
First of all, this compilation model will lead to Poor robustness !
Here we continue with the previous example , stay AppDelegate In the definition of readonly by 0x01, And the statement of this definition is in #import The statement before , So what will happen at this time ?
The compiler will also do the copy and paste operations just now , But the terrible thing is , You'll find those in the property declaration readonly Also became 0x01, This triggers the compiler to report an error !
@interface ADBannerView : UIView
@property (nonatomic, 0x01) ADAdType adType;
- (id)initWithAdType:(ADAdType)type
/* ... */
@end
@implementation AppDelegate
//...
@end
In the face of this mistake , You might say it's the developer's own problem .
exactly , We usually use a fixed prefix when we declare a macro . But there are always accidents in life , Isn't it ?
Suppose someone doesn't follow this rule , In different order of introduction , You may get different results , For this kind of error checking , It's still disturbing . however , This is not the most disturbing , Because there's more Dynamic macro The existence of , Feel stifled ing.
So this depends on
Abide by the agreementCome onAvoid problemsSolutions for , It doesn't solve the problem fundamentally , This also reflects from the sideRobustness of compiled modelsRelativePoor.
Expansibility
That's the question of robustness , Let's look at the problem of expansibility .
Apple The company's Mail App Did an analysis , The picture below is Mail All the... In this project .m Sorting of files , The horizontal axis is Document number sorting , The vertical axis is file size .

You can see that the size distribution range of these files composed of business codes is very wide , There are at least a few kb, The biggest can be 200+ kb, But on the whole , Probably 90% All the codes are in 50kb Below that order of magnitude , Even less .
If we go to a core document of the project ( Core files are files that other files may need to rely on ) Added a pair of iAd.h References to documents , What does it mean for other documents ?
The core file here refers to the file that other files may need to rely on
This means that other documents will also put iAd.h What's included in it , Of course , The good news is ,iAd This SDK I only have 25KB The size of the left and right .

But you have to know iAd And will depend on UIKit Such a component , This is a 400KB+ The big guy of

therefore , How to put it? ?
stay Mail App All of the code in needs to cover this first 425KB The header file content of , Even if you only have one line of code Hello World.
If you think it's already frustrating , Then there's something more damaging to you , because UIKit Compared with macOS Upper Cocoa A series of big gift bags , It's so small ,Cocoa The big package is UIKit Of 29 times ……
So if you put this data in the chart above , You'll find that the real business code is File Size The specific gravity on the shaft is really insignificant .
So this is one of the problems caused by poor expansibility !
Obviously , We can't introduce code in this way , Suppose you have M Source file And each file will import N Head file , According to the explanation just now , The time to compile them would be M * N, It's very scary !
remarks : What's mentioned in the article iAd Components for 25KB,UIKit The component is about 400KB, macOS Of Cocoa Components are UIKit Of
29And so on , yes WWDC 2013 Session 404 Advances in Objective-C The data published in , As functions iterate , Look at it now , These data may have been too small , stay WWDC 2018 Session 415 Behind the Scenes of the Xcode Build Process Mentioned in theFoundation Components, The number of header files it containsGreater than 800 individual, It's bigger than9MB.
PCH(PreCompiled Header) It's a double-edged sword
To optimize the problems mentioned above , A compromise technology was born , It is PreCompiled Header.
We can often see that the header files of some components appear frequently , for example UIKit, And it's easy to think of an optimization point , Can we do something about it , How to avoid compiling the same content repeatedly ?
And this is PCH by Precompile process Improvements brought about by !
Its general principle is , Before we compile any .m Before document , The compiler will start with PCH Precompile the contents of , Turn it into a kind of The binary intermediate format is cached , It is convenient for subsequent use . When you start compiling .m When you file , if necessary PCH The content that has been compiled in , Direct reading that will do , No need to compile again .
Although this technology has certain advantages , But in practice , There are still many problems .
First , It has a certain cost of maintenance , For the most part
The burden of history is heavyFor the components of , The reference relationship in the projectSorting it out is very troublesome, On this basis, we should sort out reasonable PCH The content is even more troublesome , At the same time, as the version continues to iterate , Which header files need to be removed PCH, Which header files need to be moved into PCH It's going to get more and more troublesome .secondly ,
PCHMay triggerNamespace is contaminatedThe problem of , because PCH The imported header file will appear everywhere in your code , This may be superfluous , such as iAd It should be in some ad related code , It doesn't have to be in help related code at all ( That's the logic that has nothing to do with advertising ), But when you put it in PCH in , It means that everything in the component will introduce iAd Code for , Including help pages , This may not be the result we want !
If you want to know more about it ·PCH The dark side of ·, Recommended reading 4 Ways Precompiled Headers Cripple Your Code , It has been quite comprehensive and thorough .
therefore PCH It's not a perfect solution , It can speed up compilation in some scenarios , But there are also flaws !
Clang Module The coming of !
In order to solve the problems mentioned above ,Clang Put forward Module The concept of , An introduction to it can be found in Clang Official website Found on the .
Simply speaking , You can understand it as a Description of the component , Contains the right Interface (API) and Realization (dylib/a) Description of , meanwhile Module The product of is Independent compilation Coming out , Different Module Will not affect Of .
At the time of actual compilation , The compiler will create a New space , Use it to store compiled Module product . If you refer to a Module Words , The system will first find out whether there are corresponding intermediate products in this list , If you can find , The file has been compiled , The intermediate product is directly used , If not , Then compile the referenced header file , And will product Add to the corresponding In the space in preparation for Reuse .
In this way
Compile modelNext , The reference to Module Will only beCompile once, And inIn operationWill not affect each other , This fundamentally solves the problem of robustness and expansibility .
Module It's not troublesome to use , It's also a quotation iAd This component , You just need to write like this .
@import iAd;
At the level of use , This will be equivalent to the previous #import <iAd/iAd.h> sentence , But I can use Clang Module Load the entire iAd Components . If you only want to import specific files ( such as ADBannerView.h), The original writing was #import <iAd/ADBannerView.h>, Now it can be written as :
@import iAd.ADBannerView;
In this way, you will iAd This component's API Import into our application , At the same time, this kind of writing is more in line with semantic (semanitc import).
Although there is little difference between this way of introduction and the previous way of writing , But they are very different in nature ,Module Can't “ Copy and paste ” The contents of the header file , I won't let @import What's exposed API Local by the developer Context tampering , As mentioned earlier #define readonly 0x01.
here , If you think it's about Clang Module The description is still too abstract , We can go further and explore how it works , And that brings in a new concept —— modulemap.
No matter what ,Module It's just a right Abstract description of components only , and modulemap It's this The concrete presentation of the description , It gives a structured description of all the files in the framework , Here is UIKit Of modulemap file .
framework module UIKit {
umbrella header "UIKit.h"
module * {
export *}
link framework "UIKit"
}
This Module Defines the Umbrella Header file (UIKit.h), Need to export Son Module( all ), And the need for Link The frame name of (UIKit), It is through this document that , Let the compiler know Module Logical structure of And How the header file structure is associated .
Maybe someone else will be curious , Why I've never seen @import How to write ?
This is because Xcode The compiler for can convert #import sentence Auto convert to Module Identification of the @import sentence , So as to avoid the manual modification of developers .
The only thing developers need to do is Turn on the relevant compilation options .

For the compilation options above , What developers need to pay attention to is :
Apple Clang - Language - ModulesinEnable ModuleOption meansReference system librariesWhen ,Whether to adopt Module In the form of.
and
PackagingInsideDefines ModuleRefer toWhether the component written by the developer adopts Module In the form of.
Said so much , I think you should be right #import,pch, @import With a certain concept . Of course , If we go further , There may also be the following questions :
about
Did not open Clang ModuleComponents of the feature ,Clang How to find the header file ? In the process of finding system header files and non system header files , What's the difference ?about
Enabled Clang ModuleComponents of the feature ,Clang How does it decide to compile the current component Module Well ? And what are the details of the build , And how to find these Module Of ? And the search system Module And unsystematic Module What's the difference ?
To answer these questions , We might as well practice it first , Look at the above theoretical knowledge in reality .
So it's like this
In the previous chapter , We will focus on the introduction of the principle , And in this chapter , We're going to take a look at what these precompile steps actually look like .
#import The appearance of
Suppose our source code style is as follows :
#import "SQViewController.h"
#import <SQPod/ClassA.h>
@interface SQViewController ()
@end
@implementation SQViewController
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [ClassA new];
NSLog(@"%@", a);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
Want to see what the code looks like after it's precompiled , We can do it in Navigate to Related Items Button Preprocess Options

Now that you know how to view precompile What it looks like after , Let's take a look at the code in use #import, PCH and @import after , What will it be like ?
Here we assume that the imported header file , namely ClassA The inner part of is as follows :
@interface ClassA : NSObject
@property (nonatomic, strong) NSString *name;
- (void)sayHello;
@end
adopt preprocess You can see that the code is roughly as follows , Here for the convenience of display , The useless code was deleted . Remember to put Build Setting in Packaging Of Define Module Set to NO, Because the default value is YES, And that leads us to turn on Clang Module characteristic .
@import UIKit;
@interface SQViewController : UIViewController
@end
@interface ClassA : NSObject
@property (nonatomic, strong) NSString *name;
- (void)sayHello;
@end
@interface SQViewController ()
@end
@implementation SQViewController
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [ClassA new];
NSLog(@"%@", a);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
Look at this ,#import It's really a Copy & Write.
PCH The true face of
about CocoaPods Components created by default , Generally close PCH Related functions , For example, I created SQPod Components , its Precompile Prefix Header The default value of the function is NO.

To see the effect of precompiling , We will Precompile Prefix Header To change the value of YES, And compile the whole project , By looking at Build Log, We can find that compared to NO The state of , During compilation , Added a step , namely Precompile SQPod-Prefix.pch Steps for .

By looking at this command -o Parameters , We can see that the product is called SQPod-Prefix.pch.gch The file of .

This file is PCH The product of precompiled translation , At the same time When compiling real code , Will pass -include Parameter to introduce .
See you again Clang Module
In the open Define Module after , The system will automatically create the corresponding modulemap file , This can be found in Build Log Look for to .
Its contents are as follows :
framework module SQPod {
umbrella header "SQPod-umbrella.h"
export *
module * {
export * }
}
Of course , If the system automatically generates modulemap It's not what you want , We can also use our own files , At this time, it is only necessary to Build Setting Of Module Map File Fill in the file path in the options , Corresponding clang The command parameter is -fmodule-map-file.

Finally, let's have a look at Module The shape of the compiled product .
Here we build a model called SQPod Of Module , Give it to someone named Example Engineering use of , By looking at -fmodule-cache-path Parameters of , We can find Module Of Cache path .

After entering the corresponding path , We can see the following file :

The suffix is pcm Is built Binary intermediates .
Now? , We not only know the basic theory of precompiling , I also started to check the product of precompile in real environment , Now we are going to answer the two questions mentioned before !
Break the casserole and ask after all
On the first question
about
Did not open Clang ModuleComponents of the feature ,Clang How to find the header file ? In the process of finding system header files and non system header files , What's the difference ?
Early Clang During compilation , Search mechanism of header file Or based on Header Seach Path Of , It's also a mechanism that most people are familiar with , So we won't go into details , Just a simple review .
Header Search Path Is a build system provided to the compiler Important parameter , Its purpose is to compile the code , Provides information for the compiler to find the corresponding header file path , By looking up Xcode Of Build System Information , We can see that there are three related settings Header Search Path、System Header Search Path、User Header Search Path.

The difference is also very simple ,
System Header Search PathIs aimed atSystem header fileSet up , Usually refers to<>File introduced in this way ,User Header Search PathIt's forNon system header fileSet up , Usually refers to “” File introduced in this way , andHeader Search PathThere will be no restrictions , It is suitable for any kind of header file reference .
It sounds complicated , But about the way of introduction , There are only four forms :
#import <A/A.h>
#import "A/A.h"
#import <A.h>
#import "A.h"
We can understand this problem in two dimensions , One is introduced Symbolic form , The other is introduced Content form .
Introduced
Symbolic form: Generally speaking , The introduction of double quotation marks (“A.h” perhaps “A/A.h”) Is used forFind the local header file, You need to specify a relative path , The introduction of angle brackets (<A.h> perhaps <A/A.h>) yesGlobal references, ItsThe path is provided by the compiler, Such as reference system library , But as the Header Search Path The addition of , Let the difference be watered down .Introduced
Content form: aboutX/X.handX.hThese two introduced content forms , The former means in the correspondingSearch Pathin , findCatalog A And in A Directory lookup A.h, The latter means inSearch PathThe lookupA.hfile , andNot necessarily limited to A Directory, as forWhether to search recursivelyDepends on whether the directory option is enabledrecursivePattern

In many projects , Especially based on CocoaPods Projects developed , We don't know how to distinguish System Header Search Path and User Header Search Path, Instead, all the header file paths are added to Header Search Path in , This causes us to refer to a header file , It will no longer be limited to the agreements mentioned above , Even in some cases , All the four methods mentioned above can introduce a specified header file .
Header Maps
As the project iterates and develops , The original index mechanism of header file still faces some challenges , So ,Clang The authorities have come up with their own solutions .
To understand this thing , We're going to start with Build Setting In the open Use Header Map Options .
And then in Build Log Get the compile command of the corresponding file in the corresponding component , And add... At the end -v Parameters , To see the secret of its operation :
$ clang <list of arguments> -c SQViewController.m -o SQViewcontroller.o -v
stay console In the output of , We'll find an interesting passage :
Through the graph above , We can see that the compiler shows the order of searching for header files and the corresponding path , And in these paths , We saw something strange , The suffix is .hmap The file of .
that hmap What the hell is this ?
When we turn on Build Setting Medium Use Header Map After the options , Meeting Automatic generation A share of Mapping table of header file name and header file path , And this mapping table is hmap file , But it is a kind of Binary format file , Some people call it Header Map. All in all , Its core function is to let the compiler find the location of the corresponding header file .
To better understand it , We can go through milend Write a small tool hmap Let's look at the content .
While executing the relevant commands ( namely hmap print) after , We can find these hmap The structure of the information stored in is as follows :
We need to pay attention to , Key values of the mapping table It's not simple File name and absolute path , Its content will change with the use of the scene , For example, the header reference is in "..." In the form of , still <...> In the form of , Or at Build Phase in Header Configuration of .

thus , I think you should understand , Once on Use Header Map After the options ,Xcode Priority will be given to hmap The mapping table Find the path of the header file in the , Only if it can't be found , To go to Header Search Path Path traversal search provided in .
Of course, this technology is nothing new , stay Facebook Of buck There's something similar in the tools , It's just that the file type becomes HeaderMap.java The appearance of .
Find the header file of the system library
The above process makes us understand that in Header Map Under technology , How does the compiler find the corresponding header file , How to index the files in the system library ? for example #import <Foundation/Foundation.h>
Think back to the last section console The output of , It takes the form of :
#include "..." search starts here:
XXX-generated-files.hmap (headermap)
XXX-project-headers.hmap (headermap)
#include <...> search starts here:
XXX-own-target-headers.hmap (headermap)
XXX-all-target-headers.hmap (headermap)
Header Search Path
DerivedSources
Build/Products/Debug (framework directory)
$(SDKROOT)/usr/include
$(SDKROOT)/System/Library/Frameworks(framework directory)
We will find that , Most of these paths are used to find Non system library files Of , That is, the header file introduced by the developer himself , There are only two paths related to the system library :
#include <...> search starts here:
$(SDKROOT)/usr/include
$(SDKROOT)/System/Library/Frameworks.(framework directory)
When we look for Foundation/Foundation.h This file , We will first judge whether there is Foundation This Framework.
$SDKROOT/System/Library/Frameworks/Foundation.framework
next , We will enter Framework Of Headers Find the corresponding header file in the folder .
$SDKROOT/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h
If the corresponding file is not found , The indexing process is interrupted here , And end the search .
The above is the header file search logic of the system library .
Framework Search Path
Up to now , We've explained how to rely on Header Search Path、hmap And so on , It also introduces how to find the system library (System Framework) The working mechanism of header file .
Is this the search mechanism for all header files ? The answer is No , In fact, we have another kind of Header file search mechanism , It is based on Framework This file structure does .
For developers themselves Framework, May exist “private” The header file , For example, in podspec In the use private_header_files Description file for , When these files are built , Will be put in Framework In the file structure PrivateHeaders Catalog .
So there are PrivateHeaders The directory Framework for ,Clang Checking Headers After the directory , Will go to PrivateHeaders Look for a matching header file in the directory , If neither directory has , Will end the search .
$SDKROOT/System/Library/Frameworks/Foundation.framework/PrivateHeaders/SecretClass.h
But it is precisely because of this working mechanism , It's going to raise a particularly interesting question , That's when we use Framework In this way, we can bring in some kind of “Private” Component of the header file , We can always import this header file in the following way !
What about? , Isn't that amazing , This is described as “Private” Why is the header file not private ?
The reason is , Or because Clang How it works , What then? Clang To design this seemingly strange working mechanism ?
uncover Public、Private、Project The true face of
Actually, you can see , I wrote in the last paragraph , Will all Private The words are in double quotation marks , It's a hint , We Misinterpreted Private The meaning of .
So this “Private” What do you mean ?
stay Apple 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 a little , Namely Build Phases - Headers I mentioned Public and Private It means yes For external use The header file , And put them in the final product respectively Headers and PrivateHeaders Directory , and Project The header file in is Not for external use Of , also It won't be on the The final In the product .
If you keep going through some material , for example StackOverflow - Xcode: Copy Headers: Public vs. Private vs. Project? and StackOverflow - Understanding Xcode’s Copy Headers phase, You'll find that in the early days Xcode Help Of Project Editor In Chapter , There is a passage called Setting the Role of a Header File Paragraph of , It records in detail 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 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 the product, but it’s marked “private”. 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.
thus , We should have a thorough understanding of Public、Private、Project The difference between . In short ,Public still In the ordinary sense Public,Private Then represent In Progress The meaning of , as for Project It's just in the usual sense Private meaning .
that CocoaPods in Podspec Of Syntax There's also public_header_files and private_header_files Two fields , Whether their true meaning is the same as Xcode The conflict of concepts in ?
Let's read it carefully here Interpretation of official documents , In especial private_header_files Field .

We can see ,private_header_files The meaning here is to say , It itself is relative to Public In terms of the , These header files are not intended to be exposed to users , And there's no documentation , But in When building , Will appear in the final product , Only that it has not been Public and Private Annotated header file , Will be considered a real private header file , And not in the final product .
In fact, it seems that ,CocoaPods about Public and Private My understanding is and Xcode Consistent with the description in , Two places Private It's not what we usually understand Private, Its original meaning should be Developers are ready to open up , But not quite Ready The header file , More like a In Progress The meaning of .
therefore , If you really don't want to expose certain header files , Please don't use Headers Inside Private perhaps podspec Inside private_header_files 了 .
thus , I think you should fully understand Search Path The search mechanism and slightly strange Public、Private、Project Set the !
be based on hmap Optimize Search Path The strategy of
In the section finding header files for system libraries , We go through -v Parameter to see the search order for the header file :
#include "..." search starts here:
XXX-generated-files.hmap (headermap)
XXX-project-headers.hmap (headermap)
#include <...> search starts here:
XXX-own-target-headers.hmap (headermap)
XXX-all-target-headers.hmap (headermap)
Header Search Path
DerivedSources
Build/Products/Debug (framework directory)
$(SDKROOT)/usr/include
$(SDKROOT)/System/Library/Frameworks(framework directory)
hypothesis , We didn't turn it on hmap Words , All searches depend on Header Search Path perhaps Framework Search Path, Then this will come out 3 Seed problem :
- The first question is , In some mega projects , Suppose that the dependent components are 400+, Then the index path will reach 800+ individual ( One copy Public route , One copy Private route ), At the same time, the search operation can be regarded as a kind of IO operation , And we know that IO Operation is also a time-consuming operation , that , Such a large number of time-consuming operations will inevitably lead to the increase of compilation time .
- The second question is , In the process of packing , If Header Search Path Too much too long , An error that causes the command line to be too long , Which leads to the failure of command execution .
- Third question , When importing the header file of the system library ,Clang I will search the path of the system library after traversing the directory mentioned above , That is to say
$(SDKROOT)/System/Library/Frameworks(framework directory), I.e. the one in frontHeader SearchThe more paths , The longer it takes , It's quite uneconomic .
So if we turn on hmap after , Can we solve all the problems ?
It doesn't actually , And based on CocoaPods In the case of project management , It will bring new problems . Here is one based on CocoaPods Build a full source project , Its overall structure is as follows :
First ,Host and Pod It's our two Project,Pods Under the Target The type of product is Static Library.
secondly ,Host There's going to be one with the same name at the bottom Target, and Pods There will be n+1 individual Target, among n Depending on the number of components you rely on , and 1 It's a man named Pods-XXX Of Target, Last ,Pods-XXX This Target And the product of that will be Host Inside Target Depend on .
The whole structure looks like this :
At this point we are going to PodA All the files in the library are in Header Of Project Type in the .

Based on Framework Under the search mechanism of , We can't introduce it in any way ClassB Of , Because it's not in Headers Catalog , Not at all PrivateHeader Directory .
But if we turn it on Use Header Map after , because PodA and PodB All in Pods This Project Next , To satisfy the Header Of Project Definition , adopt Xcode Automatically generated hmap The file will take this path , So we can still be PodB China and Israel #import "ClassB.h" Way to introduce .
And this behavior , I think it should be the result that most people don't want , So once it's on Use Header Map, combining CocoaPods The model for managing engineering projects , We are very likely to have some misuse of private header files , And the essence of the problem is Xcode and CocoaPods The conflict of ideas between the project and the header file .
besides ,CocoaPods There's also something confusing about dealing with header files , The logic of creating header file is as follows :
The product of construction is
FrameworkUnder the circumstances- according to podspec Inside public_header_files The contents of the field , Set the corresponding header file to Public type , And put it on Headers in .
- according to podspec Inside private_header_files The contents of the field , Set the corresponding file to Private type , And put it on PrivateHeader in .
- Set the remaining undescribed header files to Project type , And not in the final product .
- If podspec It's not marked Public and Private When , All files will be set to Public type , And put it on Header in .
The product of construction is
Static LibraryUnder the circumstances- Regardless of podspec How to set up public_header_files and private_header_files, The corresponding header file will be set to Project type .
- stay Pods/Headers/Public All files declared as public_header_files The header file .
- stay Pods/Headers/Private All header files are saved in , Whether it's public_header_files perhaps private_header_files Describe to , Or those that are not described , This directory contains all the header files of the current component .
- If podspec It's not marked Public and Private When ,Pods/Headers/Public and Pods/Headers/Private The contents of are the same and will contain all header files .
Because of this mechanism , It leads to another interesting problem .
stay Static Library In the case of , Once we turn it on Use Header Map, The type of all header files in the component is Project The situation of , This hmap It will only contain #import "A.h" Key value reference of , That is to say, only #import "A.h" The way to hit hmap The strategy of , Otherwise, they will all pass Header Search Path Find the relevant path .
And we know that , When referring to other components , It's usually used #import <A/A.h> Way to introduce . As for why it's done this way , On the one hand, this writing method will clarify the origin of the header file , Avoid problems , On the other hand, it's also this way that we can start or not Clang Module Switch at will , Of course, there is one more thing ,Apple stay WWDC More than once, we have suggested that developers use this method to introduce header files .
And then the topic above , So in the Static Library In the case of #import <A/A.h> When header files are introduced in this standard way , Turn on Use Header Map It doesn't speed up compilation , And this is also Xcode and CocoaPods The conflict of ideas between the project and the header file .

In this way , although
hmapThere are various advantages , But inCocoaPodsIt seems out of place in our world , I can't play my own advantage .
Then there is really no way to solve it ?
Of course , There is a way to solve the problem , We can do it ourselves based on CocoaPods Under the rules hmap file .
Let's take a simple example , By traversing PODS Content in the directory to build index table content , With the help of hmap Tool generation header map file , And then Cocoapods stay Header Search Path Delete the path generated in , Just add one to our own generated hmap File path , Finally close Xcode Of Ues Header Map function , That is to say Xcode Automatic generation hmap The function of , such , We've achieved a simple , be based on CocoaPods Of Header Map function .
And on this basis , We can also use this function to achieve a lot of control means , for example :
- Fundamentally eliminate the possibility of private files being exposed .
- Unifies the reference form of the header file
- …
at present , We have developed a set of cocoapods plug-in unit , It's called cocoapods-hmap-prebuilt, It was jointly developed by the author and colleagues .
Said so much , Let's see how it works in real projects !
After full source code compilation test , We can see that the benefits of this technology in increasing speed are obvious , With meituan and comments App For example , The whole link duration can be improved 45% above , among Xcode Packing time can improve 50%.
On the second question
For opened
Clang ModuleComponents of the feature ,Clang How does it decide to compile the current component Module Well ? And what are the details of the build , And how to find these Module Of ? And the search system Module And unsystematic Module What's the difference ?
First , Let's make a point , Clang How does it decide to compile the current component Module Well ?
With #import <Foundation/NSString.h> For example , When we come across this header file :
First of all Framework Of
HeadersFind the corresponding header file in the directory , And then we'll get toModulesDirectory lookupmodulemapfile .

here ,Clang I'll look it up modulemap Contents of Li , have a look NSString Is it Foundation This Module Part of .
// Module Map - Foundation.framework/Modules/module.modulemap
framework module Foundation [extern_c] [system] {
umbrella header "Foundation.h"
export *
module * {
export *
}
explicit module NSDebug {
header "NSDebug.h"
export *
}
}
Obviously , Through here Umbrella Header, We can be in Foundation.h Find NSString.h Of .
// Foundation.h
…
#import <Foundation/NSStream.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTextCheckingResult.h>
…
thus ,Clang I will judge NSString.h yes Foundation This Module And do the corresponding compilation work , That means #import <Foundation/NSString.h> It's going to start from the previous textual import Turn into module import.
Module The construction details of
The above content solves whether to build Module, And here we're going to elaborate on building Module The process of !
Before the build starts ,Clang Will create a Completely independent space To build Module, In this space there will be Module All the documents involved , In addition, no other file information will be brought in , And this is also Module One of the key factors for good robustness .
however , That doesn't mean we can't influence Module Uniqueness , What really affects its uniqueness is the parameters of its construction , That is to say Clang What follows the command , This will continue to be discussed later , Let's stop here .
When we're building Foundation When , We will find that Foundation It depends on some components , This means that we also need to build the Module.
But obviously , We will find that these dependent components also have their own dependencies , In these dependencies , It's very likely that there will be duplicate references .
here ,Module The reuse mechanism of It shows its advantages , We can reuse the previously built Module, Instead of having to create or reference again and again , for example Drawin Components , The location to save these cache files is the one mentioned in the previous chapter pcm Where type files are .
We mentioned earlier Clang The parameters of the command really affect Module Of Uniqueness , What is the specific principle ?
Clang The corresponding compilation parameters will be checked once Hash, What will be obtained Hash Value as Module The name of the cache folder , What needs to be noted here is , Different parameters and values lead to different folders , So I want to make the most of Module cache , You have to make sure that the parameters don't change .
$ clang -fmodules —DENABLE_FEATURE=1 …
## The generated directory is as follows
98XN8P5QH5OQ/
CoreFoundation-2A5I5R2968COJ.pcm
Security-1A229VWPAK67R.pcm
Foundation-1RDF848B47PF4.pcm
$ clang -fmodules —DENABLE_FEATURE=2 …
## The generated directory is as follows
1GYDULU5XJRF/
CoreFoundation-2A5I5R2968COJ.pcm
Security-1A229VWPAK67R.pcm
Foundation-1RDF848B47PF4.pcm
Here we have a general idea of the components of the system module Build mechanism , It's also open Enable Modules(C and Objective-C) The core working principle of .
The mystery of the Virtual File System(VFS)
For system components , We can do it in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/System/Library/Frameworks Find it in the catalog , Its directory structure is like this :
in other words , about System components for , structure Module The whole process is based on such a complete file structure :
That is to say
FrameworkOfModulesLook formodulemap, stayHeadersDirectoryLoad header file.
For components created by users themselves ,Clang And how to build Module What about ?
Usually our development directory looks like the following , It doesn't Modules Catalog , either Headers Catalog , No more modulemap file , Looks like Framework There is also a big difference in the file structure of .

under these circumstances ,Clang It can't be built according to the mechanism mentioned above Module Of , Because in this file structure , There is no such thing as Modules and Headers Catalog .
To solve this problem ,Clang A new solution is proposed , be called Virtual File System(VFS).
Simply speaking , Through this technology ,Clang You can create a virtual file on the existing file structure Framework File structure , And then let Clang Follow the build guidelines mentioned earlier , Well done Module Compilation of , meanwhile VFS It also records the real location of the file , So that when something goes wrong , Expose the real information of the file to the user .
To learn more about VFS, We are still from Build Log Find some details in !

In the compilation parameters above , We can find one -ivfsoverlay Parameters of , see Help explain , You can see that its function is Pass a to the compiler VFS Describe the file and override the real file structure information .
-ivfsoverlay <value> Overlay the virtual filesystem described by file over the real file system
Follow this lead , Let's look at the file that this parameter points to , It's a yaml File format , After some tailoring of the content , Its core content is as follows :
{
"case-sensitive": "false",
"version": 0,
"roots": [
{
"name": "XXX/Debug-iphonesimulator/PodA/PodA.framework/Headers",
"type": "directory",
"contents": [
{
"name": "ClassA.h", "type": "file",
"external-contents": "XXX/PodA/PodA/Classes/ClassA.h"
},
......
{
"name": "PodA-umbrella.h", "type": "file",
"external-contents": "XXX/Target Support Files/PodA/PodA-umbrella.h"
}
]
},
{
"contents": [
"name": "XXX/Products/Debug-iphonesimulator/PodA/PodA.framework/Modules",
"type": "directory"
{
"name": "module.modulemap", "type": "file",
"external-contents": "XXX/Debug-iphonesimulator/PodA.build/module.modulemap"
}
]
}
]
}
Combined with the contents mentioned above , It's not hard to see that it describes such a file structure :
Borrow a
Real existenceFolder tosimulation FrameworkInsideHeaders Folder, In this Headers The folder is called PodA-umbrella.h and ClassA.h Etc , But these virtual files and external-contents Point to the real file , In the same way and Modules The folder and what's in it module.modulemap file .
In this way , A virtual Framework The directory structure was born ! here Clang Finally, we can create Module 了 !
Swift coming
There is no header file Swift
The previous chapter , We talked a lot C Language Department Precompiled knowledge , Under this system , File compilation is separate , When we want to quote from other documents , The corresponding header file must be introduced .

And for Swift In this language , It doesn't have the concept of a header file , For developers , This really saves the repetitive work of writing header files , But it also means , The compiler does extra work to find interface definitions and needs to keep an eye on interface changes !
For a better explanation Swift and Objective-C How to find each other's method statements , Let's introduce an example here , In this case, there are three parts :
- The first part is one ViewController Code for , It contains a View, among PetViewController and PetView All are Swift Code .
- The second part is a App Agent for , It is Objective-C Code .
- The third part is a single test code , Used to test... In the first part ViewController, It is Swift Code .
import UIKit
class PetViewController: UIViewController {
var view = PetView(name: "Fido", frame: frame)
…
}
#import "PetWall-Swift.h"
@implementation AppDelegate
…
@end
@testable import PetWall
class TestPetViewController: XCTestCase {
}
Their relationship is roughly as follows :
In order to make the code compile successfully , The compiler will face the following 4 A scenario :
The first is to look for statements , This includes looking for the present Target Method statement in (PetView), Also from Objective-C The declaration in the component (UIViewController perhaps PetKit).
Then there is the generation interface , This includes being Objective-C Interface used , It also includes being treated by other Target (Unit Test) The use of Swift Interface .
First step - How to find Target Inside Swift Method statement
Compiling PetViewController.swift when , The compiler needs to know PetView The type of initialization constructor for , To check that the call is correct .
here , The compiler loads PetView.swift File and parse the contents , The purpose of this is to make sure that the initialization constructor really exists , And get the relevant type information , In order to PetViewController.swift To verify .
The compiler does not check inside the initialization constructor , But it still does some extra work , What does that mean ?
And Clang compiler The difference is ,Swiftc When compiling , Will be the same Target The rest of it Swift The file is parsed once , It is used to check whether the interface part associated with the compiled file meets the expectation .
And we know that , The compilation of each file is independent , And the compilation of different files can be carried out in parallel , So that means that every file compiled , We need to put the current Target The rest of the files in are used as interfaces , Recompile once . Equal to any file , Throughout the compilation process , Only 1 It's used as a production .o The input of products , The rest of the time will be repeatedly parsed as an interface file .
But in the Xcode 10 in the future ,Apple This compilation process has been optimized .
At the same time as parallel as possible , The file is Group compilation , This avoids Group Duplicate parsing of files in , It's just different Group There will be repeated file parsing between files .

And the logic of this grouping operation , Just mentioned some extra operations .
thus , We should know Target How to look inside Swift Method is declared .
The second step - How to find Objective-C Method declarations in components
Back to the first code , We can see PetViewController It is inherited from UIViewController, And it also means that our code will work with Objective-C Code to interact , Because most system libraries , for example UIKit etc. , Or use Objective-C Compiling .
On this issue ,Swift It's different from other languages !
Generally speaking , Two different languages need to provide an interface mapping table when they are mixed , for example JavaScript and TypeScript When it was mixed up .d.ts file , such TypeScript We'll know JavaScript Method in TS In the world .
However ,Swift There is no need to provide such an interface mapping table , It's free of developers for every Objective-C API Declare that it is Swift In the world , So how does it do that ?
It's simple ,Swift Compiler will Clang Most of the functionality of is contained in its own code , This enables us to Module In the form of , Direct reference Objective-C Code for .
Since it's through Module The form of introduction Objective-C, that Framework File structure is the best choice , There are three scenarios for the compiler to look for method declarations :
For most Target for , When you import a Objective-C Type of Framework when , The compiler will pass modulemap Inside Header Information search method statement .
For an existing Objective-C, And then there is Swift Code Framework for , The compiler will start from the current Framework Of Umbrella Header Find method statements in , So as to solve their own compilation problems , That's because usually modulemap Will Umbrella Header As one's own Header value .
about App perhaps Unit Test Type of Target, Developers can do this for Target establish Briding Header To import what you need Objective-C The header file , Then find the required method declaration .
But we should know Swift The compiler is getting Objective-C In the process of coding , It's not the original general Objective-C Of API Exposed to the Swift, It's about doing something “Swift turn ” The changes to the , For example, the following Objective-C API It's going to be transformed into a simpler form .
This transformation process is not any advanced technology , It's just hard coding on the compiler , If you are interested , Can be in Swift Open source library to find the corresponding code - PartsOfSpeech.def
Of course , The compiler also gives developers self definition “API Appearance ” The right to , If you're interested in this piece , Read my other article - WWDC20 10680 - Refine Objective-C frameworks for Swift, There's a lot of remodeling in the bread Objective-C API The technique of .
But here's another thing , If you're confused about the generated interface , You can see that the compiler is Objective-C Generated Swift Interface .
The third step - Target Internal Swift How the code works for Objective-C Providing interfaces
The earlier Swift How code references Objective-C Of API, that Objective-C How to quote Swift Of API Well ?
In terms of use , We all know Swift The compiler will automatically generate a header file for us , In order to Objective-C Introduce the corresponding code , Like in the second code PetWall-Swift.h file , This header file is usually generated automatically by the compiler , The composition of a name is Component name -Swift In the form of .

But how did it come about ?
stay Swift in , If a class inherits NSObject Class and API By @objc Keyword annotation , It means that it will Exposed to the Objective-C Code using .
But for the App and Unit Test Type of target for , This is automatically generated Header Will contain access levels of Public and internal Of API, This makes the same Target Internal Objective-C Code can also access Swift in internal Type of API, This is all Swift The default access level for code .
But for the Framework Type of Target for ,Swift The automatically generated header file will only contain Public Type of API, Because this header file will be used externally as a build product , So it's like internal Type of API Is not included in this file .
Be careful , This mechanism will lead to Framework Type of Target in , If Swift I want to expose some API For internal Objective-C Code using , It means that API It has to be exposed to the outside world , That is, the access level must be set to Public.
So the compiler automatically generates API What is it like , What are the characteristics ?

Above is intercepted a section of automatically generated header code , On the left is the original Swift Code , The right side is automatically generated Objective-C Code , We can see in the Objective-C In the class , There is one named SWIFT_CLASS The macro , take Swift And Objective-C Two classes in are associated .
If you pay a little attention , You will find that the current component name is bound in the associated garbled code (PetWall), The purpose of this is to avoid conflicts between two components of the same name class at run time .
Of course , You can also go to @objc(Name) Keyword passes an identifier , By means of this identifier, it is controlled in Objective-C The name of the , If you do , Developers need to ensure that the converted class name does not conflict with other class names .
This is basically Swift How to look like Objective-C Expose the mechanism of the interface , If you want to know more about the origin of this document , You need to look at step four .
Step four - Swift Target How to generate for external Swift Interface used
Swift Adopted Clang module Idea , And a series of improvements have been made in combination with its own language characteristics .
stay Swift in ,Module Is the distribution unit of the method declaration , If you want to quote the appropriate method , We have to introduce the corresponding Module, We also mentioned before Swift Our compiler contains Clang Most of it , So it's also compatible Clang Module Of .
So we can introduce Objective-C Of Module, for example XCTest, Can also be introduced Swift Target Generated Module, for example PetWall.
import XCTest
@testable import PetWall
class TestPetViewController: XCTestCase {
func testInitialPet() {
let controller = PetViewController()
XCTAssertEqual(controller.view.name, "Fido")
}
}
In the introduction of swift Of Module after , The compiler will Deserialization A suffix is .swiftmodule The file of , And through the content of this file to understand the relevant interface information .
for example , The following is an example , In this unit test , The compiler loads PetWall Of Module, And look for PetViewController Method statement for , This ensures that its creation behavior is as expected .

It looks like the first step Target Look inside Swift The method declaration looks like , It's just that we're going to analyze Swift Document steps , It's analytic Swiftmodule The file" .
But here's the thing , This Swfitmodule Files are not text files , It's a binary format of content , Usually we can build the product in Modules Find it in the folder .

stay Target In the process of compiling , For the whole Target Of Swiftmodule file It doesn't happen at once , every last Swift Every file will generate a Swiftmodule file , The compiler will aggregate these files , Finally, a complete , representative Whole Target Of Swiftmodule, It's based on this file , The compiler has constructed for external use Objective-C The header file , That's the header file mentioned in the third step .

But as the Swift The development of , Some changes have taken place in the working mechanism of this part .
We mentioned that earlier Swiftmodule A document is a kind of Binary format file , And this file format will contain some data structures inside the compiler , Different compilers produce Swiftmodule Files are incompatible with each other , And that makes a difference Xcode The product built is not universal , If you're interested in the details , You can read Swift Two official articles in the community Blog:Evolving Swift On Apple Platforms After ABI Stability and ABI Stability and More, There will be no discussion here .
To solve this problem ,Apple stay Xcode 11 Of Build Setting A new compilation parameter is provided in Build Libraries for Distribution, Just like the name of this compilation parameter , When we turn it on , The built product will no longer be affected by the compiler version , So how does it do that ?
To solve this version dependence on compilers ,Xcode It provides a new product on the construction product ,Swiftinterface file .
The contents of this document and Swiftmodule Very similar , It's all current Module Inside API Information , however Swiftinterface In order to Record in the form of text , Instead of Swiftmodule The binary way of .
This makes Swiftinterface The behavior of is the same as the source code , Later versions of Swift Compilers can also import previously created Swiftinterface file , Use it the same way you use source code .
To learn more about it , Let's see Swiftinterface What it really looks like , Here's a .swift Document and .swiftinterface A comparison of the files .
stay Swiftinterface In file , The following points need to be noted
- The file will contain some meta information , For example, file format version , Compiler information , and Swift The compiler imports it as a module into the required subset of the command line .
- The file will only contain Public The interface of , It doesn't include Private The interface of , for example currentLocation.
- The file will only contain method declarations , Instead of including method implementations , for example Spacesship Of init、fly Other methods .
- The file will contain all implicitly declared methods , for example Spacesship Of deinit Method ,Speed Of Hashable agreement .
in general ,Swiftinterface The file will be stable across all versions of the compiler , The main reason is that the interface file contains all the information of the interface level , There is no need for the compiler to make any inference or assumption .
Okay , By now we should have known Swift Target How is it generated for external Swift The interface used .
What do these four steps mean ?
this Module The 1 pet. Module
Through the example above , I think you should be able to clearly feel Swift Module and Clang Module It's not exactly one thing , Although they have a lot in common .
Clang Module It's for C A technology of the language family , adopt modulemap Documents to organize .h Interface information in the file , The intermediate is in binary format pcm file .
Swift Module It's for Swift A technology of language , adopt Swiftinterface Documents to organize .swift Interface information in the file , Intermediate product binary format Swiftmodule file .
So after making clear these concepts and relationships , We are building Swift A product of a component , You will know which files and parameters are not required .
For example, when your Swift Components don't want to expose their own API To the outside Objective-C If the code is used , Can be Build Setting in Swift Compiler - General Inside Install Objective-C Compatiblity Header Parameter set to NO, The compilation parameters are SWIFT_INSTALL_OBJC_HEADER, There is no generation at this time <ProductModuleName>-Swift.h Files of type , This means that external components cannot be Objective-C Refer to components in the same way Swift Code API.

And if you don't have it in your components at all Objective-C Code time , You can take Build Setting in Packaging in Defines Module Parameter set to NO, Its compilation parameters are DEFINES_MODULE, There is no generation at this time <ProductModuleName>.modulemap Files of type .

Swift and Objective-C Three mixed up “ tricks ”
Based on the example just now , We should understand Swift How to find others at compile time API Of , And how it exposes itself API Of , And this knowledge is the basic knowledge to solve the mixed editing process , To deepen the impact , We can draw it into 3 A flow chart .
When Swift and Objective-C The files are all in one App perhaps Unit Test Type of Target in , Different types of files API The search mechanism is as follows :
When Swift and Objective-C Documents are different Target in , For example, different Framework in , Different types of files API The search mechanism is as follows :

When Swift and Objective-C The files are all in one Target in , For example, the same Framework in , Different types of files API The search mechanism is as follows :

For the third flowchart , The following additional explanation is needed :
- because Swiftc, That is to say Swift The compiler , Contains most of Clang function , That's included Clang Module, By using the existing modulemap file ,Swift The compiler can easily find the corresponding Objective-C Code .
- Compared to the second process , In the third process modulemap It's inside the component , And in the second process , If you want to refer to the Objective-C Code , You need to introduce... From other components modulemap Only documents can .
- So based on this consideration , Not in the process 3 Mark in modulemap.
structure Swift New ideas for products
In the previous chapter , We mentioned that Swift Seek Objective-C The way , It says , except App perhaps Unit Test Type of Target Outside , The rest is through Framework Of Module Map To find Objective-C Of API, So if we don't want to use Framework In the form of ?
For now , This is in Xcode It can't be realized directly in , The reason is simple ,Build Setting in Search Path There's nothing in the options modulemap Of Search Path Configuration parameters .

Why does it have to be modulemap Of `Search Path Well ?
Based on what we learned earlier ,Swiftc Contains Clang Most of the logic of , In terms of precompiling ,Swiftc It only includes Clang Module The pattern of , And there's no other model , therefore Objective-C I want to expose myself API You have to go through modulemap To complete .
And for Framework This standard folder structure ,modulemap The relative path of the file is fixed , It's in Modules Directory , therefore Xcode Based on this standard structure , Directly built in the relevant logic , There's no need to expose these configurations again .
From the perspective of component developers , He just needs to care about modulemap Whether the content of the project is in line with expectations , And whether the path meets the specifications .
From the perspective of users of components , He just needs to introduce the corresponding Framework You can use the corresponding API.
This just needs to be configured Framework The way , Avoid configuration Header Search Path, It also avoids configuration Static Library Path, It can be said to be a very friendly way , If you will modulemap Open your configuration , On the contrary, it seems unnecessary .
Well, if we leave Xcode, Put aside Framework The limitation of , There are other ways to build Swift The product ?
The answer is yes , This requires the help of the above-mentioned VFS technology !
Suppose our file structure looks like this :
├── LaunchPoint.swift
├── README.md
├── build
├── repo
│ └── MyObjcPod
│ └── UsefulClass.h
└── tmp
├── module.modulemap
└── vfs-overlay.yaml
among LaunchPoint.swift Refer to the UsefulClass.h One of them is public API, And it creates dependency .
in addition ,vfs-overlay.yaml The file remaps the existing file directory structure , It reads as follows :
{
'version': 0,
'roots': [
{
'name': '/MyObjcPod', 'type': 'directory',
'contents': [
{
'name': 'module.modulemap', 'type': 'file',
'external-contents': 'tmp/module.modulemap'
},
{
'name': 'UsefulClass.h', 'type': 'file',
'external-contents': 'repo/MyObjcPod/UsefulClass.h'
}
]
}
]
}
thus , We pass the following order , You can get LaunchPoint Of Swiftmodule、Swiftinterface Wait for the documents , Specific examples can be found in Github Link on - manually-expose-objective-c-API-to-swift-example
swiftc -c LaunchPoint.swift -emit-module -emit-module-path build/LaunchPoint.swiftmodule -module-name index -whole-module-optimization -parse-as-library -o build/LaunchPoint.o -Xcc -ivfsoverlay -Xcc tmp/vfs-overlay.yaml -I /MyObjcPod
So what does that mean ?
That means , Only the corresponding .h Document and .modulemap The file can be done Swift The construction of binary products , Instead of relying on Framework Entity of . meanwhile , about CI system , In building the product , You can avoid downloading useless binary products (.a file ), This improves compilation efficiency to some extent .
If you don't quite understand the above , We can talk about .
for example , about PodA In terms of components , It depends on itself PodB Components , When using the original build method , We need to pull PodB The integrity of the component Framework product , This will include Headers Catalog ,Modules The necessary content in the catalog , Of course, there will also be a binary file (PodB), But in actual compilation PodA In the process of components , We don't need B Binary files in components , And this allows you to pull the whole Framework The file is redundant .
And with the help of VFS technology , We can avoid pulling extra binaries , Further improve CI The compilation efficiency of the system .
summary
Thank you for your patience in reading , thus , The whole article is finally over , Through this article , I think you should :
- understand Objective-C Three working mechanisms of precompile of , among Clang Module It achieves the real semantic introduction , It improves the robustness and scalability of compilation .
- stay Xcode Of Search Path The various technical details of the use of hmap technology , By loading the mapping table, a large number of duplicate IO operation , It can improve compilation efficiency .
- Processing Framework The index of the header file of , Always search first Headers Catalog , Re search PrivateHeader Catalog .
- understand Xcode Phases Building the system ,Public On behalf of the public header ,Private It means that user perception is not required , But the physical existence of the file , and Project Representatives should not be perceived by users , Files that don't exist physically .
- Don't use Framework In the case of #import <A/A.h> When header files are introduced in this standard way , stay CocoaPods Upper use hmap It doesn't speed up compilation .
- adopt cocoapods-hmap-built plug-in unit , It can save the whole link time of large projects 45% above ,Xcode Time saving in packaging 50% above .
- Clang Module The build mechanism of ensures that it is not affected by context ( Independent compilation space ), High reuse efficiency ( Depending on the resolution ), Uniqueness ( Parameter hashing ).
- The system components are implemented through the existing Framework The file structure implements the build Module The basic conditions of , Rather than system components through VFS Virtual similar Framework file structure , Then we have the conditions of compiling .
- Can be shallow will Clang Module Inside .h/m,.moduelmap,.pch The concept of is corresponding to Swift Module Inside .swift,.swiftinterface,.swiftmodule The concept of
- Understand three universal Swift And Objective-C Mix method
- same Target Inside (App perhaps Unit type ), be based on -Swift.h and -Bridging-Swift.h.
- same Target Inside , be based on -Swift.h and Clang Own ability .
- Different Target Inside , be based on -Swift.h and module.modulemap.
- utilize VFS Mechanism construction , You can build Swift Avoid downloading useless binary products in the production process , Further improve compilation efficiency .
Reference documents
Apple - WWDC 2013 Advances in Objective-C
Apple - WWDC 2018 Behind the Scenes of the Xcode Build Process
Apple - WWDC 2019 Binary Frameworks in Swift
Apple - WWDC 2020 Distribute binary frameworks as Swift packages
Swift org - Evolving Swift On Apple Platforms After ABI Stability
Swift org - ABI Stability and More
StackOverflow - #import using angle brackets < > and quote marks “ ”
StackOverflow - Xcode: Copy Headers: Public vs. Private vs. Project?
StackOverflow - Understanding Xcode’s Copy Headers phase
Xcode Help - What are build phases?
Xcode Build Settings
Big Nerd Ranch - Manual Swift: Understanding the Swift/Objective-C Build Pipeline
Big Nerd Ranch - Build Log Groveling for Fun and Profit: Manual Swift Continued
Big Nerd Ranch - Build Log Groveling for Fun and Profit, Part 2: Even More Manual Swift
Quality Coding - 4 Ways Precompiled Headers Cripple Your Code
try! Swift Tokyo 2018 - Exploring Clang Modules
milen.me - Swift, Module Maps & VFS Overlays
Author's brief introduction
Siqi , Pen name SketchK, US group review iOS The engineer , Currently responsible for the mobile terminal CI/CD Work in the field and in the platform Swift Technology related issues .
Xutao , Meituan iOS The engineer , He is currently responsible iOS Issues related to end development and efficiency improvement .
Frost leaf ,2015 Joined Meituan in 1986 , Have been engaged in Hybrid Containers 、iOS Basic components 、iOS Develop tool chain and client continuous integration portal system .
边栏推荐
- Gson typeadapter adapter
- Please use the NLTK Downloader to obtain the resource
- ICer技能01正则匹配
- Non return to zero code NRZ
- Shadertoy basic teaching 02. Drawing smiling faces
- Pads and flash symbols in cadence
- 独立站聊天机器人有哪些类型?如何快速创建属于自己的免费聊天机器人?只需3秒钟就能搞定!
- With the arrival of intelligent voice era, who is defining AI in the new era?
- Shadertoy基础教学02、画笑脸
- const理解之二
猜你喜欢

2 万字 + 20张图|细说 Redis 九种数据类型和应用场景

Abnova PSMA磁珠解决方案

ICer技能02makefile脚本自跑vcs仿真

一款MVC5+EasyUI企业快速开发框架源码 BS框架源码

在Pycharm中对字典的键值作更新时提示“This dictionary creation could be rewritten as a dictionary literal ”的解决方法

mysql json

Abnova ABCB10(人)重组蛋白说明书

Gson typeadapter adapter

The paddepaddle model is deployed in a service-oriented manner. After restarting the pipeline, an error is reported, and the TRT error is reported

ICER skills 03design compile
随机推荐
Kali 安装之腾讯云经验遇到坑
const理解之二
PCB placing components at any angle and distance
Common concepts and terms in offline warehouse modeling
2020:VL-BERT: Pre-training of generic visual-linguistic representation
ICer技能03Design Compile
Mini Homer——几百块钱也能搞到一台远距离图数传链路?
微信小程序实例开发:跑起来
Kail infiltration basic literacy basic command
altium designer 09丝印靠近焊盘显示绿色警告,如何阻止其报警?
Abnova blood total nucleic acid purification kit protocol
美团好文:从预编译的角度理解Swift与Objective-C及混编机制
接收传来得文件并下载(简单用法)a标签
Welcome to the CSDN markdown editor
Pads and flash symbols in cadence
Question bank and answers of 2022 hoisting machinery safety management examination
ICer技能02makefile脚本自跑vcs仿真
Bootstrap drive, top switching power supply and Optocoupler
如何解决独立站多渠道客户沟通难题?这款跨境电商插件一定要知道!
laravel 8.4 路由问题,结尾处是编辑器左侧对照表,小白可看懂