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如下图所示

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的名称

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容