Write IOS mixed SDK from scratch (Part 2)

Time:2022-5-27

Pretend to write a preface

Background: a large SDK with a long history. Of course, this history is Objective-C. Of course, there is no problem with this, and it serves tens of millions of users every day, but unfortunately, Apple has a problemStoreKit2It’s normal to say that every year, Apple’s father produces a lot of APIs. Unfortunately, it only supports swift writing, and I guess this API will only become the norm, soSwiftThere is no time to delay.

Result: write in the middle of the night

Warm tip: the example code of this article has been uploaded to GitHub and is at the end of the article.

Compilation environment: xcode13.0 swift5.5 arm64

If there is any error, please let me know in the comment area.

1、 Swift calls internal OC class

  1. Red solid line: for those that have been completed and cannot be changed, we need to reduce the external files and reduce the exposure.

  2. Black solid line: the downward reference of OC side and swift side has been realized at present.

  3. Blue dotted line: the mutual call between OC and swift internal classes, which is also the most important work of mixing.

Problem background: there are a large number of basic tool classes in the existing OC SDK. We need to make swift internal classes call these OC basic classes.

  • Such a tool class has single function and low coupling, which can effectively reduce the amount of swift code.
  • At present, the internal classes written by swift and OC internal classes are not called, soWe only consider the case where swift calls OC internal classes.

So our current task:Make testswift2 call the onlyforswift2 method of testoc2

|Plan A:

  1. stayTargetMedium selectionSWSDKBuild PhasestakeTestOC2fromprojectDrag inpublic
  2. staySWSDK.hJoin in#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 ("testswift 2: swift inner class")
        TestOC2.onlyForSwift2()
    }
}

Although the goal is achieved, butTestOC2.hTherefore, it is also disclosed. It should be understood that in the actual project, our operations are not one or two, so too much code will be exposed, which is not desirable. (and if you call it once, you have to perform such an operation. It’s too painful iii#, #)

|Plan B:

Contact the modulemap file we mentioned earlier to map the library inheritance.

  1. Create a new head H file and add the header file to import:
#ifndef Header_h
#define Header_h

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

#endif /* Header_h */
  1. Create a new file and change its name tomodule.modulemapAnd then write the following in it.
module OCTest{
    umbrella header "Header.h"
    export *
}
  1. After searching for swift compiler – search paths in building setting, enter the directory path in import paths. As shown in the figure below:

  2. ⚠ Note: prefixheader The PCH file may be invalid. You may need to delete the file and the path in setting.

  3. The following are the contents of testoc2 and 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


//--------Calling testoc2 in swift-----------
import UIKit
import OCTest

class TestSwift2: NSObject {
    static func run() -> Void {
        Print ("testswift 2: 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] swifttest2 call + [testoc2 onlyforswift2]
2021-11-04 21:28:35.115717 + 0800 testdemo [27264:7623092] testoc2 called + [testoc3 runfor_testoc2]
2021-11-04 21:28:35.115734+0800 TestDemo[27264:7623092] +[TestOC2 run]

Plan B can obviously solve our problem. It is not exposed and can be called by swift, but there are still some problems.

|Q&A:

  1. Testswift2 only refers to testoc2. Why do you need to import testoc3 into the header h?
Because testoc2 references testoc3, if testoc3 is not imported, the compilation will report an error and cannot continue.
  1. Why is PCH invalid?
Because module is a derivative of PCH, it is caused by PCH's inability to solve the increasing number of precompiled files< https://www.jianshu.com/p/4969b1c47bc8 >It is explained in.
Summarized as:
    The code cannot be directly copied and pasted in other projects because there is no same PCH file, which may cause that the dependent file cannot be found.
    The dependent header file is hidden or the transmission dependency is very deep and not intuitive.
    Without effective management, dependence will gradually expand and eventually grow.
  1. If more and more header files are imported into modulemap in the future, how to avoid problems like PCH?

Submodule is referenced in modulemap. We can distinguish the role of each module to import the required module explicitly or implicitly.

2、 Map module solution

Let’s talk about 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.

(omitted) the Chinese don’t cheat the Chinese: the key link between the module and the header file is described by the module mapping, which describes how the set of existing header files is mapped to the (logical) structure of the module. For example, you can imagine a module STD covering the C standard library. Each C standard library header file (< stdio. H >, < stdlib. H >, < math. H >) will put their respective APIs into the corresponding sub modules (std.io, std.lib, std.math, etc.). Having a list of header files as part of the STD module allows the compiler to build the STD module as a separate entity, and having a mapping from the head file name to the (sub) module allows the #include instruction to be automatically converted to module import.

Lu Xun said: “a good memory is not as good as a bad pen, and it is better to read it ten thousand times than to read it once” (escape)

  1. In fact, it can be understood as follows: the SDK is so huge and there are so many files and classes. It is always based on one yourproductname H outward docking is inseparable from modulemap.
framework module SWSDK {
  umbrella header "XXX.h"
 
  export *
  module * { export * }
}
  1. The above is a pure ocframework, so there is one inside SWSDK.h Documents.
  2. The following is a mix of swift in OC, because the internal OC class references swift and needs #import < XXX / xxx swift h> At this time, 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
}

Introduce the use of module

At present, our modulemap looks like this:

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

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

  • There are many documents, which is not conducive to management.

  • It is impossible to distinguish code functions, and there may be classes with different functions such as network class, tool class and system extension class.

  • Some classes do not need to be compiled frequently and need not be used occasionally.

More usage 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 *
    }
    
}
├── Header.h
Other // add a new folder
│   ├── OtherOne.h
│   ├── OtherOne.m
│   ├── TestNetTool.h
│   └── TestNetTool.m
├── SWSDK.h
├── Swift
│   ├── TestSwift.swift
│   └── TestSwift2.swift
├── TestB. H // add a file
├── TestB.m
├── ...//  Original document
└── module.modulemap

New definitionOCTest Module, including onesubmoduleAnd oneB module;

  • Other is a directory, which contains two header files, otherone and testnettool.

  • The search path of module takes modulemap as the root path, and then fill in the relative path. If there is a directory under other, it should be written as follows: umbrella "other / directory name"

  • submoduleOne more decorationexplicit. Means thismoduleACalls that need to be displayed can be used.

  • We don’t need the same import module header. such asModule BTestb andTestNetToolPS: the * * testnettool * * in the path cannot access testb

  • A header file can have differentModuleInside, for exampleTestNetToolexistencesubmoduleAndBYes.

#import "TestB.h"

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

@end

Enter swift and call OC module

import UIKit
import OCCommon
import OCTest
import OCTest.submodule

class TestSwift2: NSObject {
    static func run() -> 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] connect 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]
  1. If not explicitly calledOCTest.submodule, thenOtherone.run()Alarm:Cannot find 'OtherOne' in scope

  2. Want to use a childmodule submodule, we have to indicateimport OCFile.submoduleTo use.

  3. onlyimport OCTest.submoduleYou can also use module B

Other keywords

In addition, there are some other keywords that may be used.
config_macros, export_as, private conflict, framework, requiresexclude, header, textual explicit,link,umbrella, extern, module, use export
1.link framework “MyFramework”
Indicates other frameworks that depend on However, it should be noted that even if you write this, it will not be automatically introduced for you at present, which is similar to the effect of annotation. It’s a bit like Microsoft VISAUL studio’s #pragma comment (lib…)
2.framework module COFile
It shows that this module conforms to the Darwin style framework. At present, the Darwin style framework is only used for Mac OS and IOS Interested students can check it.
3.module OCFile [system]
Indicate that this is a system module. When clang compiles, it will consider that it uses the system header, so the warning is ignored. Method similar to #pragma GCC system_ header. Because the system header often cannot fully follow the C syntax, the warning information in all header files is often not displayed unless #warning is displayed
4.module OCFile [system] [extern_c]
It indicates that the C code in module can be used by C + +

The article ends here. Without the following teacher’s article, there will be no this article. Thank you

https://www.jianshu.com/p/4969b1c47bc8

https://www.jianshu.com/p/d5ca6f0b9ec8

https://www.jianshu.com/p/b4f88651f069

https://www.jianshu.com/p/ce49d8f32f77

https://www.jianshu.com/p/691438e37df7

The following is the demo address of this article, which can be compiled on xcode13.0
Point me! I’m demo

Recommended Today

Lesson 01: Flink’s application scenario and architecture model

Flink series Lesson 01: Flink’s application scenario and architecture model Lesson 02: introduction to Flink wordcount and SQL implementation Lesson 03: Flink’s programming model compared with other frameworks Lesson 04: Flink’s commonly used dataset and datastream APIs Lesson 05: Flink SQL & Table programming and cases Lesson 06: Flink cluster installation, deployment and ha configuration […]