iOS:Module
前言:
一个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如下图所示
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的名称