iOS:Module

作者: MonKey_Money | 来源:发表于2021-01-30 18:36 被阅读0次

    1. Module-最小的代码单元

    一个Module是机器代码和数据的最小单元,可以独立于其他代码单元进行链接,
    通常,Module是通过编译单个源文件生成的目标文件。例如,当前的test.m被编译成目标文件test.o时,当前的目标文件就代表一个Module
    但是,有一个问题,Module在调用的时候会产生开销,比如我们在使用一个静态库的时候。
    导入文件时如果使用include的,每次编译的时候就会编译一个我们include的头文件,导入资源的浪费。我们现在使用的import(module)导入头文件,导入的头文件会预先编译成二进制,再有文件导入时就不会重新编译。

    1.1,实测module

    //A.h文件
    #ifdef ENABLE_A
    void a() {}
    #endif
    //B.h文件
    #import "A.h"
    //module.modulemap文件
    module A {
      header "A.h"
    }
    
    module B {
      header "B.h"
      export A
    }
    //use.c文件
    #import "B.h"
    void use() {
    #ifdef ENABLE_A
      a();
    #endif
    }
    

    我们使用clang编译

    // -fmodules:允许使用module语言来表示头文件
    // -fmodule-map-file:module map的路径。如不指明默认module.modulemap
    // -fmodules-cache-path:编译后的module缓存路径
    clang  -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o
    

    我们查看prebuilt->2Q2IP2MFAAABM文件可以看到两个pcm文件,这两个文件就是预编译好的,如果其他文件再引入A和B就不用重新编译了。

    1.2.查看AFNetworking文件的modulemap文件

    framework module AFNetworking { //声明framework的module名称为AFNetworking
    //导入文件的集合
      umbrella header "AFNetworking-umbrella.h"
      export * //把引入的头文件重新导出。
      module * { export * } //把导入头文件修饰成子module,并把符号全部导出
    }
    

    其他module的操作,点这里
    我们开启module之后无论我们使用include,import或者@import,编译的使用都会被优化成module形式,就是同一个文件只会被编译一次。

    1.3.实操

    我们创建一个framework,名字为MyOCFramework,再创建一个主工程名字为MyTestApp,打开主工程,点击file->save as workspace,保存到主工程的同一级目录下。然后打来我们的workspace,在工程中,在没有文件被选的情况下,File->Add file to 到我们的workspace。选择我们的framework。
    编译我们的framework,能看到会在framework下自动生成Modules 文件.
    如果我们想自定义我们的module文件,我们创建modulemap文件,然后在build setting中设置module map file的路径。
    我们创建ocmodule.modulemap文件文件内容如下

    framework module MyOCFramework {
      umbrella "Headers"
    
      export *
      module * { export * }
    }
    

    module map file设置为MyOCFramework/ocmodule.modulemap,编译成功,并在framework文件中看到module.modulemap。

    2.Swift的framework和OC混编

    因为在framework中没有桥接文件,所以swift代码没法直接调用oc,我们要使用module,framework已经自动帮我们实现了。
    我们可以在swift代码中直接使用oc类,如果我们想在oc类中调用swift代码,我们需要通过module指定头文件#import <项目/项目-Swift.h>
    如果我们不想对外暴漏我们的OC类,我们可以创建swiftmodule.private.modulemap

    framework module MySwiftFramework_Private {
      explicit module MyOCClass{
          header "MyOCClass.h"
          export *
      }
    }
    

    然后在Private Module Map File 中指定路径。
    我们不能通过MySwiftFramework 的module 来访问MyOCClass,但是我们可以通过
    MySwiftFramework_Private来访问MyOCClass。
    Private Module不是真正意义上的私有,我们可以通过MySwiftFramework_Private可以访问,只是供开发者区分。

    3.Swift静态库合并

    在Xcode 9.0之后,swift开始支持静态库
    swift没有头文件的概念,那么我们外界使用swift中的public修饰的类和函数怎么办呢?Swift库引入了一个全新的文件.swiftModule
    .swiftModule包含序列化过的AST(抽象语法树),也包含SIL(Swift中间语言,Swift Intermediate Language)。
    我们可以看一下我们的framework中,Module中有一个.swiftmodule文件。
    创建两个framework库,分别为MySwiftA和MySwiftB
    两个库里有一个相同的类

    @objc open class MySwiftTeacher: NSObject {
        public func speek() {
            print("speek!")
        }
        @objc public func walk() {
            print("walk!")
        }
    }
    

    并把两个静态库编译后的framework放到products目录下脚本

    cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"
    

    合并两个静态库

    libtool -static MySwiftA.framework/MySwiftA MySwiftB.framework/MySwiftB -o libMySwiftC.a
    //日志警告,两个静态库都包含MySwiftTeacher.o
    

    我们通过ar -t libMySwiftC.a查看libMySwiftC.a中的目标文件

    __.SYMDEF
    MySwiftA_vers.o
    MySwiftTeacher.o
    MySwiftB_vers.o
    MySwiftTeacher.o
    

    我们手动组合MySwiftC库


    image.png

    配置build setting文件

    HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/MySwiftC/MySwiftA/Headers" "${SRCROOT}/MySwiftC/MySwiftB/Headers"
    OTHER_CFLAGS = $(inherited) "-fmodule-map-file=${SRCROOT}/MySwiftC/MySwiftA/module.modulemap" "-fmodule-map-file=${SRCROOT}/MySwiftC/MySwiftB/module.modulemap"
    SWIFT_INCLUDE_PATHS = $(inherited) "${SRCROOT}/MySwiftC/MySwiftB" "${SRCROOT}/MySwiftC/MySwiftA"
    

    4.OC映射到Swift方式

    为了让oc代码在swift使用中规范,

    4.1使用宏

    NS_SWIFT_NAME(<#name#>)
    NS_REFINED_FOR_SWIFT 在swift方法中, 编译器会在名称前加上
    _

    4.2.使用apinotes文件

    官方文档
    前面是项目或者sdk的名称后缀是apinotes,

    ---
    Name: OCFramework
    Classes:
    - Name: LGToSwift
      SwiftName: ToSwift
      Methods:
      - Selector: "changeTeacherName:"
        Parameters:
        - Position: 0
          Nullability: O
        MethodKind: Instance
        SwiftPrivate: true
        # Availability: nonswift
        #AvailabilityMsg: "prefer 'deinit'"
      - Selector: "initWithName:"
        MethodKind: Instance
        DesignatedInit: true
    

    5.module 相关的 build setting 参数

    5.1对module自身的描述:

    DEFINES_MODULE:YES/NO,module 化需要设置为 YES
    MODULEMAP_FILE:指向 module.modulemap 路径
    HEADER_SEARCH_PATHS:modulemap 内定义的 Objective-C 头文件,必须在 HEADER_SEARCH_PATHS 内能搜索到
    PRODUCT_MODULE_NAME:module 名称,默认和 Target name 相同

    5.2对外部module的引用

    FRAMEWORK_SEARCH_PATHS:依赖的 Framework 搜索路径
    OTHER_CFLAGS:编译选项,可配置依赖的其他 modulemap 文件路径 -fmodule-map-file={modulemap_path} HEADER_SEARCH_PATHS:头文件搜索路径,可用于配置源码中引用的其他 Library 的头文件 OTHER_LDFLAGS:依赖其他二进制的编译依赖选项 SWIFT_INCLUDE_PATHS:swiftmodule 搜索路径,可用于配置依赖的其他 swiftmodule OTHER_SWIFT_FLAGS:Swift 编译选项,可配置依赖的其他 modulemap 文件路径 -Xcc -fmodule-map-file={modulemap_path}

    相关文章

      网友评论

        本文标题:iOS:Module

        本文链接:https://www.haomeiwen.com/subject/dgzhtltx.html