iOS:Module

2021-02-17  本文已影响0人  浅墨入画

前言:

一个Module是机器代码和数据的最小单元,可以独立于其他代码单元进行链接,
通常,Module是通过编译单个源文件生成的目标文件。例如,当前的test.m被编译成目标文件test.o时,当前的目标文件就代表一个Module
但是,有一个问题,Module在调用的时候会产生开销,比如我们在使用一个静态库的时候。

一. Module

1.1 #include 与 #import 区别
举例 use.c 中引入 A.h B.h文件,那么编译use.c的时候,A.h B.h会一块进行编译,如果现在 use1.c 中也引入 A.h B.h,那么A.h B.h又会进行一次编译,这就是传统#include的编译方式
这个时候就体现出了module的作用,也就是#import,会提前把A.h B.h编译成二进制文件,需要的时候拿来使用,再有文件导入时就不会重新编译。

/* A.h */
#ifdef ENABLE_A
void a() {}
#endif
/* B.h */
#import "A.h"
/* use.c */
#import "B.h"
void use() {
#ifdef ENABLE_A
  a();
#endif
}
/* module.modulemap文件 */
/* modulemap 用来描述头文件与module之间映射的关系 */
/* A代表A.h  B代表B.h */
module A {
  header "A.h"
}
module B {
  header "B.h"
// 导出,把B.h引用的头文件也一并导出,也可以用 export *,把所有引用的头文件一并导出
  export A
}
// 进入Cat文件夹,把use.c文件编译成use.o文件
# -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

A.h B.h 编译之后的产物,这两个文件就是预编译好的,如果其他文件再引入A和B就不用重新编译了,如下图所示

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

其他module的操作点这里
开启module之后无论我们使用#include,#import,@import,最终都会被转换成@import写法,编译时都会被优化成module形式,就是同一个文件只会被编译一次。
1.3 module操作
创建MulitProject.xcworkspace如下图所示

image.png image.png 接下来把LGOCFramework,LGSwiftFramework库添加到MulitProject.xcworkspace,如下图所示 image.png 手动指定modulemap文件 image.png 我们创建LGOCFramework.modulemap文件文件内容如下
framework module LGOCFramework {
    // umbrella<目录>
    umbrella header "LGOCFramework.h"
  
    explicit module LGTeacher {
        header "LGTeacher.h"
        export *
    }
    explicit module LGStudent {
        header "LGStudent.h"
        export *
    }
}

编译成功,并在framework文件中看到module.modulemap文件

二. Swift库使用OC代码

在framework中没有桥接文件,所以swift代码没法直接调用oc,我们要使用module,build setting中配置Module Map File路径即可使用
自定义LGSwiftFramework.modulemap内容如下

framework module LGSwiftFramework {
    umbrella "Headers"
    export *
}

我们可以在swift代码中直接使用oc类,如果我们想在oc类中调用swift代码,我们需要通过module指定头文件#import <项目/项目-Swift.h>
如果我们不想对外暴漏我们的OC类,我们可以创建LGSwiftFramework.private.modulemap,内容如下

framework module LGSwiftFramework_Private {
    module LGOCStudent {
        header "LGOCStudent.h"
        export *
    }
}

然后在Private Module Map File 中指定路径。
我们不能通过LGSwiftFramework 的module 来访问LGOCStudent,但是我们可以通过
LGSwiftFramework_Private来访问LGOCStudent。
Private Module不是真正意义上的私有,我们可以通过LGSwiftFramework_Private可以访问,只是供开发者区分
小结
swift调用oc方法有3种方式
第一种:直接配置LGSwiftFramework.modulemap来使用#import <LGSwiftFramework/LGOCStudent>
第二种:配置LGSwiftFramework.private.modulemap来使用 @import LGSwiftFramework_Private.LGOCStudent
第三种:swift与oc约定协议,swift调用协议,协议再调用oc。这里把协议暴露出来,达到oc隐藏的目的

三. Swift静态库合并

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

创建swiftFramework,编译之后 show in finder,可以看到Modules中多生成一个LGSwiftFramework.swiftmodule目录,这个目录下多生成一个x86_64.swiftmodule文件
x86_64-apple-ios-simulator.swiftdoc:这个文档可以删除


image.png

创建两个framework库,分别为LGSwiftA和LGSwiftB
两个库里有一个相同的类

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

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

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

进入products目录下,合并两个静态库

libtool -static LGSwiftA LGSwiftB -o libLGSwiftC.a
//日志警告,两个静态库都包含LGSwiftTeacher.o

查看libLGSwiftC.a中的目标文件

$ ar -t libLGSwiftC.a
__.SYMDEF
LGSwiftA_vers.o
LGSwiftTeacher.o
LGSwiftB_vers.o
LGSwiftTeacher.o
我们手动组合LGSwiftC库,因为文件有冲突,使用这种目录结构,可是防止Headers文件内部存在的冲突 image.png

创建新工程LGApp 使用上面的LGSwiftC库,LGApp工程配置LGApp.Debug.xcconfig文件

HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers" "${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers"
// OTHER_CFLAGS:传递给用来编译C或者OC的编译器,当前就是clang
OTHER_CFLAGS="-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap" "-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap"
// SWIFT_INCLUDE_PATHS: 传递给SwiftC编译器,告诉他去下面的路径中查找module.file
SWIFT_INCLUDE_PATHS="${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework"  "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework"

并且手动组合LGSwiftC库如下

image.png 小结
在一个文件类中导入#import <LGSwiftA.h> 就会编译LGSwiftA静态库以及里面的相同类LGSwiftTeacher,导入#import <LGSwiftB.h>就会编译LGSwiftB静态库

四. OC映射到Swift方式

让oc代码在swift使用中规范
4.1使用宏
NS_SWIFT_NAME(<#name#>)
NS_REFINED_FOR_SWIFT 在swift方法中, 编译器会在名称前加上_
4.2.使用apinotes文件
参考文档
命名规则:前面是项目或者sdk名称,后缀是apinotes
.apinotes文件创建好一定要放到根目录下

#yaml 类似于 json格式
---
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

总结:

module:定义一个module
export:导出当前代表的头文件使用的头文件
export * :匹配目录下所有的头文件
module * :目录下所有的头文件都当作一个子module
explicit : 显式声明一个module的名称

上一篇下一篇

猜你喜欢

热点阅读