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库
配置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}