Pretend to write a preface

Background: Large SDKS with a long history, objective-C of course. Of course, there is no problem with this, and it serves tens of millions of users every day, but unfortunately Apple released a StoreKit2, it is normal that Apple dad should release a lot of apis every year. Unfortunately, it only supports Swift writing, and I expect this API will only become the norm, so swiftization is urgent.

Result: Midnight writing

Tips: The example code of this article has been uploaded to Github at the end of the article.

Compilation environment: Xcode13.0 Swift5.5 ARM64

Please let me know in the comments section.

Swift invokes the internal OC class

  1. Solid red line: completed and unchangeable, we need this to reduce external files and reduce exposure.

  2. Solid black line: downreferences to the OC side and Swift side that have been implemented so far.

  3. Dotted blue lines: OC and Swift internal class calls to each other, this is also the most important work of remix.

Background: There are a number of basic utility classes in the EXISTING OC SDK, and we need to make Swift internal classes call these OC base classes.

  • Such a tool class has a single function and low coupling degree, which can effectively reduce the amount of Swift code engineering.
  • At present, the internal class written by Swift, OC internal class does not have the situation of calling, so we only consider the situation of Swift calling OC internal class.

So our current task:Make TestSwift2 call the onlyForSwift2 method of TestOC2

| Plan A:

  1. inTargetSelect theSWSDKBuild PhasesTestOC2projectintopublic.
  2. inSWSDK.hadd#import <SWSDK/TestOC2.h>.
#import "TestOC2.h"

@implementation TestOC2
+ (void)run {
    NSLog(@"TestOC2: internal OC class, non-public interface.");
}
+ (void)onlyForSwift2 {
    NSLog(@ SwiftTest2 calls "% s",__func__);
}
@end

class TestSwift2: NSObject {
    static func run() -> Void {
        print("TestSwift2: Swift inner class")
        TestOC2.onlyForSwift2()
    }
}
Copy the code

Although the purpose was achieved, testoc2.h was also made public because of this. It should be understood that in real projects, we do not have one or two operations like this, so it will expose too much code, which is not desirable. (And it’s too painful to have to do this once.)

| Plan B:

Refer to the moduleMap file we talked about earlier for library inheritance mapping.

  1. Create a new heade. h file and add the import header to it:
#ifndef Header_h
#define Header_h

#import "TestOC2.h"
#import "TestOC3.h"

#endif /* Header_h */
Copy the code
  1. Create a new file and change its name tomodule.modulemap, and then write the following within it.
module OCTest{
    umbrella header "Header.h"
    export*}Copy the code
  1. After searching Swift Compiler-Search Paths in Building Setting, enter the directory path in Import Paths. As shown below:

  2. ⚠️ Note: The prefixheader. PCH file may be invalid, and you may need to delete the file and the Setting path.

  3. TestOC2, TestOC3

@implementation TestOC2
+ (void)run {
    NSLog(@"%s",__func__);
}

+ (void)onlyForSwift2 {
    NSLog(@ SwiftTest2 calls "% s",__func__);
    [TestOC3 runFor_TestOC2];
}
@end
//--------------------------------------
@implementation TestOC3

+ (void)runFor_TestOC2 {
    NSLog("TestOC2 called %s",__func__);
}

@end


//-------- call TestOC2----------- in Swift
import UIKit
import OCTest

class TestSwift2: NSObject {
    static func run() -> Void {
        print("TestSwift2: Swift inner class")
        TestOC2.onlyForSwift2()
    }
}
//
2021- 11- 04 21:28:35.115221+0800 TestDemo[27264:7623092] +[TestOC run]
SWSDK/TestSwift.swift: run()
SWSDK/TestSwift2.swift: run()
2021- 11- 04 21:28:35.115688+0800 TestDemo[27264:7623092[TestOC2 onlyForSwift2]2021- 11- 04 21:28:35.115717+0800 TestDemo[27264:7623092] TestOC2 calls +[TestOC3 runFor_TestOC2]2021- 11- 04 21:28:35.115734+0800 TestDemo[27264:7623092] +[TestOC2 run]
Copy the code

PLAN B obviously solves our problem, is not exposed and can be called by Swift, but so far there are some problems.

|Q&A:

  1. TestSwift2 references only TestOC2, why import TestOC3 into header.h?
Because TestOC2 references TestOC3, TestOC3 is not imported, and the compilation fails to continue.Copy the code
  1. Why is PCH ineffective?
Because the Module is PCH derivatives, is our inability to procure the PCH precompiled files under the condition of more and more, this explanation in < https://www.jianshu.com/p/4969b1c47bc8 >. The code cannot be copied and pasted directly in another project, because the same PCH file is not available, which may cause the dependency file not to be found. Dependency headers are hidden or pass dependencies are deep and unintuitive. Without effective management, dependencies will grow and eventually grow.Copy the code
  1. How do you avoid problems like PCH if more and more header files are imported into ModuleMap in the future?
Submodules are referenced in the ModuleMap, and we can differentiate the function of each module to import required modules explicitly or implicitly.Copy the code

Ii. ModuleMap Solution

Let’s start with module.moduleMap

| English class:

The crucial link between modules and headers is described by a module map, which describes how a collection of existing headers maps on to the (logical) structure of a module. For example, one could imagine a module std covering the C standard library. Each of the C standard library headers (<stdio.h>, <stdlib.h>, <math.h>, etc.) would contribute to the std module, by placing their respective APIs into the corresponding submodule (std.io, std.lib, std.math, etc.). Having a list of the headers that are part of the std module allows the compiler to build the std module as a standalone entity, and having the mapping from header names to (sub)modules allows the automatic translation of #include directives to module imports.

The key link between modules and header files is described by module mapping, which describes how the collection of existing header files maps to the module’s (logical) structure. For example, imagine a module, STD, that covers the C library. Each C library header file (<stdio.h>, <stdlib.h>, <math.h>, etc.) is put into the corresponding submodule (std.io, std.lib, std.math, etc.) by putting their respective API. Having a list of header files as part of an STD module allows the compiler to build STD modules as separate entities, and having a mapping of header file names to (child) modules allows #include directives to be automatically converted into module imports.

Lu Xun once said, “A bad pen is better than a good memory

  1. The SDK is so large and has so many files and classes that it is always connected to the outside with a yourProductname.h thanks to Modulemap.
framework module SWSDK {
  umbrella header "XXX.h"
 
  export *
  module * { export*}}Copy the code
  1. It’s pure OCFramework, so there’s only one inside SWSDK.h File.
  2. #import <XXX/ xxx-swift. h> is required for the OC class to reference Swift, and Modulemap also needs to inherit all Swift open classes.
framework module SWSDK {
  umbrella header "XXX.h"
 
  export *
  module * { export*}}module SWSDK.Swift {
    header "XXX-Swift.h"
    requires objc
}
Copy the code

| Describes how to use the Module

Our moduleMap currently looks like this:

module OCTest{
    umbrella header "Header.h"
    export*}Copy the code

If there are more files in the future, the following conditions cannot be met at present:

  • Numerous documents are not conducive to management.

  • There may be classes with different functions such as network class, tool class and system extension class.

  • Some classes also do not need to be imported frequently, but are used only occasionally and do not take up compilation space.

More uses of module

module OCCommon {
    umbrella header "Header.h"
    export*}/ * Header. H content # import "TestOC2. H" # import "TestOC3. H" # import "SWSDK. H" # import "TestOC. H * /


module OCTest{
    explicit module submodule {
        umbrella "Other" / / folder
        module * { export*}}module B {
        header "TestB.h"
        header "Other/TestNetTool.h"
        export*}}Copy the code
├ ─ ─ the Header. H ├ ─ ─ the Other// Add a folder│ │ ├ ─ ─ OtherOne. H ├ ─ ─ OtherOne. M │ ├ ─ ─ TestNetTool. H │ └ ─ ─ TestNetTool. M ├ ─ ─ SWSDK. H ├ ─ ─ Swift │ ├ ─ ─ TestSwift. Swift │ └ ─ ─ TestSwift2. Swift ├ ─ ─ TestB. H// Add a file├ ─ ─ TestB. M ├ ─ ─...// Original file└ ─ ─module.modulemap
Copy the code

New definition of OCTest Module, including a subModule and a B Module;

  • Other is a directory containing the OtherOne and TestNetTool header files.

  • The search path for module is moduleMap as the root path and fill in the relative path. If there is another directory under Other, write umbrella “Other/ Directory name “.

  • Submodule has a modifier explicit. This moduleA requires explicit calls to be used.

  • In the same Module, we do not need import headers. For example, TestB and TestNetTool on Module B, PS: **TestNetTool** in the path cannot access TestB.

  • A header file can be stored in different modules, such as TestNetTool in subModule and B.

#import "TestB.h"

@implementation TestB
+ (void)run {
    NSLog(@"%s",__func__);
    [TestNetTool request];
}

@end
Copy the code

Enter Swift and call OC Module

import UIKit
import OCCommon
import OCTest
import OCTest.submodule

class TestSwift2: NSObject {
    static func run(a) -> Void {
        print("(#fileID): (#function)")
        //OCCommon
        TestOC2.onlyForSwift2()
        
        //Module B
        TestNetTool.request()
        TestB.run()
        
        //submodule
        Otherone.run() 
    }
}
/ / output
2021-11-05 10:53:23.614885+0800 TestDemo[28007:7899400] +[TestOC run]
SWSDK/TestSwift.swift: run()
SWSDK/TestSwift2.swift: run()
2021-11-05 10:53:23.615341+0800 TestDemo[28007:7899400] Connects to the network.
2021-11-05 10:53:23.615365+0800 TestDemo[28007:7899400] +[TestB run]
2021-11-05 10:56:00.676746+0800 TestDemo[28029:7902689] +[OtherOne run]
Copy the code
  1. If octest.subModule is not explicitly called, otherone.run () raises an alarm: Cannot find ‘Otherone’ in scope

  2. To use subModule submodule, we must specify import ocfile. submodule.

  3. Import only octest. submodule, or Module B.

Other Keywords

There are a few other keywords that might be used. config_macros, export_as, private conflict, framework, requiresexclude, header, textual explicit,link,umbrella, extern, Module, use export 1. Link Framework” MyFramework” specifies the dependent framework. But it’s important to note that even if you do write this, it’s not going to automatically introduce a comment like effect for you right now. #pragma Comment (lib…) COFile indicates that this module conforms to the Darwin-style framework, which is currently only available on macOS and iOS. If you’re interested, you can check it out. 3. Module OCFile [system] indicates that this is a system module. When clang is compiled, it takes into account that it uses the system header and therefore ignores the warning. Method similar to #pragma GCC system_header. Module OCFile [system] [extern_c] indicates that the C code in module can be used by C++

Here is the end of the article, without the following teacher’s article would not have this article, thank 🙏

www.jianshu.com/p/4969b1c47…

www.jianshu.com/p/d5ca6f0b9…

www.jianshu.com/p/b4f88651f…

www.jianshu.com/p/ce49d8f32…

www.jianshu.com/p/691438e37…

The following is the Demo address of this article, which can be compiled on Xcode13.0. I am a Demo