当前位置:网站首页>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 Path I'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 Phases Inside Header I'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 hmap Technology 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 VFS Techniques 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 #importiAd.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 .

 Insert picture description here

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 agreement Come on Avoid problems Solutions for , It doesn't solve the problem fundamentally , This also reflects from the side Robustness of compiled models Relative Poor .

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 .

 Insert picture description here

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 .

 Insert picture description here

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

 Insert picture description here

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 29 And 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 the Foundation Components , The number of header files it contains Greater than 800 individual , It's bigger than 9MB.

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 heavy For the components of , The reference relationship in the project Sorting 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 ,PCH May trigger Namespace is contaminated The 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 model Next , The reference to Module Will only be Compile once , And in In operation Will 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 .
 Insert picture description here

The only thing developers need to do is Turn on the relevant compilation options .

 Insert picture description here

For the compilation options above , What developers need to pay attention to is :

Apple Clang - Language - Modules in Enable Module Option means Reference system libraries When , Whether to adopt Module In the form of .

and Packaging Inside Defines Module Refer to Whether 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 Module Components 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 Module Components 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

 Insert picture description here

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.

 Insert picture description here

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 .

 Insert picture description here

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

 Insert picture description here

This file is PCH The product of precompiled translation , At the same time When compiling real code , Will pass -include Parameter to introduce .
 Insert picture description here

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 .
 Insert picture description here

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.

 Insert picture description here

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 .

 Insert picture description here

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

 Insert picture description here

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 Module Components 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 PathSystem Header Search PathUser Header Search Path.

 Insert picture description here

The difference is also very simple ,System Header Search Path Is aimed at System header file Set up , Usually refers to <> File introduced in this way ,User Header Search Path It's for Non system header file Set up , Usually refers to “” File introduced in this way , and Header Search Path There 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 for Find the local header file , You need to specify a relative path , The introduction of angle brackets (<A.h> perhaps <A/A.h>) yes Global references , Its The 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 : about X/X.h and X.h These two introduced content forms , The former means in the corresponding Search Path in , find Catalog A And in A Directory lookup A.h, The latter means in Search Path The lookup A.h file , and Not necessarily limited to A Directory , as for Whether to search recursively Depends on whether the directory option is enabled recursive Pattern

 Insert picture description here

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 .
 Insert picture description here

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 :
 Insert picture description here

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 :
 Insert picture description here

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 .

 Insert picture description here

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 Pathhmap 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 .
 Insert picture description here

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 !
 Insert picture description here

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 .

 Insert picture description here

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 front Header Search The 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 :
 Insert picture description here

At this point we are going to PodA All the files in the library are in Header Of Project Type in the .

 Insert picture description here

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 Framework Under 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 Library Under 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 .

 Insert picture description here

In this way , although hmap There are various advantages , But in CocoaPods It 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 Module Components 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 Headers Find the corresponding header file in the directory , And then we'll get to Modules Directory lookup modulemap file .

 Insert picture description here

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.
 Insert picture description here

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 .
 Insert picture description here

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 :
 Insert picture description here

in other words , about System components for , structure Module The whole process is based on such a complete file structure :

That is to say Framework Of Modules Look for modulemap, stay Headers Directory Load 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 .

 Insert picture description here

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 !

 Insert picture description here

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 existence Folder to simulation Framework Inside Headers 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 .

 Insert picture description here

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 :
 Insert picture description here

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 .
 Insert picture description here

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 .
 Insert picture description here

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 .

 Insert picture description here

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 .
 Insert picture description here

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 .
 Insert picture description here

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 .
 Insert picture description here

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 .

 Insert picture description here

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 ?

 Insert picture description here

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 .
 Insert picture description here

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 .

 Insert picture description here

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 .

 Insert picture description here

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 .

 Insert picture description here

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 .
 Insert picture description here

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 .
 Insert picture description here

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 .
 Insert picture description here

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.

 Insert picture description here

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 .

 Insert picture description here

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 :
 Insert picture description here

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 :

 Insert picture description here

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 :

 Insert picture description here

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 .

 Insert picture description here

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 .
 Insert picture description here

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 .

原网站

版权声明
本文为[ReyZhang]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/174/202206230025426895.html