iOS开发进阶八:Module(模块)

module(模块):最小的代码单元

一个module是机器代码和数据的最小单位,可以独立于其他代码单位进行链接。

这句话的的解释:

通常,module是通过编译单个源文件生成的目标文件。例如:当前的test.m被编译成目标文件test.o时,当前的目标文件就代表了一个module。

但是,有一个问题,Module在调用的时候会产生开销,比如我们在使用一个静态库的时候,可以这样使用

@import TestStaticFramework;

这个静态库中可能包含了许多的.o文件。岂不是要导入很多的Module。并不需要。在静态链接的时候,也就是静态库链接到主项目或者动态库时,最后生成可执行文件或者动态库时。静态链接器可以把多个Module链接优化成一个,来减少本来多个Module直接调用的问题。

  • 问题一:我们经常讲.m的编译,那.h是怎么编译的?
  • 问题二:Module在调用的时候会产生开销,比如我们在使用一个静态库的时候。这种开销是怎么引起的?系统是怎么优化这种开销?
  • 问题三:通常我们在项目中使用#import 导入AFN,实际上需要导入很多个头文件,这其中产生了什么问题?当我们在代码中导入一个库或者引入其它头文件的时候,发生了什么事情?
  • 问题四:ModuleMap是什么?

#include#import差异

案例:现存在3个头文件:A.hB.hC.h。在B.h中导入头文件A.h,在C.h中导入头文件B.h,在C.c中导入C.h

编译时,编译器按顺序编译这些文件,

#include导入方式:

  • 先到A时,A中没有导入其它文件,只需编译A。
  • 到B时,因为B中导入了A,A又要编译一次,需要编译A和B。
  • 到C时,因为C导入了B,所以需要编译B,编译B时,因为B导入了A,A也要再次编译。即A、B、C都要编译一次。

#import导入方式:

  • 头文件会被预先编译成二进制文件,并且每个头文件只会被编译一次。此时无论有多少文件导入头文件,都不会被重复编译。

验证#include导入方式

可以用指令看看编译器在预处理阶段帮我们做了哪些事情:

clang -E use.c
Clang预处理中头文件处理过程.png

无论文件中是#import还是#includeclang预编译出来的结果是一样的,意思就是执行的流程是一样的。但是在具体到每个步骤的时候,存在差异:#import导入时,直接使用预先编译好的二进制文件。

每次包含标头时,编译器都必须可传递地预处理和解析该标头及其包含的每个标头中的文本。必须对应用程序中的每个翻译单元重复此过程,这涉及大量的冗余工作。

#include伪指令被预处理程序视为文本包含,因此在包含时必须接受任何活动的宏定义。如果任何活动宏定义碰巧与库中的名称冲突,则可能会破坏库API或导致库头本身的编译失败。

此外,导入模块时将自动提供使用该模块所需的任何链接器标志

每个模块都被解析为一个独立的实体,因此它具有一致的预处理器环境。

此外,在遇到导入声明时,当前的预处理器定义将被忽略,

@import上面的声明导入std模块的全部内容(其中将包含例如整个C或C ++标准库),并在当前翻译单元中提供其API。要仅导入模块的一部分,可以使用点语法来特定特定的子模块

模块会自动将#include指令转换为相应的模块导入

关于开销的问题:

如果只编译一个C.c文件,A、B、C这3个头文件都需要编译一次,两种导入方式无差异。但是真正的项目中,依赖关系通常都很复杂,使用import做到每个文件只编译一次,就可以节省开销。

在具有N个翻译单元和每个翻译单元中包含M个标头的项目中,即使M个标头中的大多数在多个翻译单元之间共享,编译器仍在执行M x N个工作。

std.io模块仅编译一次,并且将模块导入转换单元是恒定时间操作(与模块系统无关)。因此,每个软件库的API仅解析一次,从而将M x N编译问题减少为M + N问题。

用modulemap验证#import导入方式

参考文档

https://clang.llvm.org/docs/APINotes.html

https://clang.llvm.org/docs/Modules.html#export-declaration

# -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

新建module.modulemap文件:

准备验证modulemap的使用.png

module.modulemap用来描述头文件与module之间映射的关系

  • 定义了名称为A和B的两个module
  • module A中,定义了header A.h,表示module A和A.h的映射关系
  • module B中,定义了header B.h,和A同理。export A表示将B.h导入的A.h头文件重新导出

用clang指令使用fmodules方式生成目标文件:

fmodules方式编译后生成的pcm二进制文件.png

moduleXcode中是默认开启的。如果在Build Settings中,将Enable Modules设置为NO,导入头文件将不能使用@import方式。开启module后,项目中导入头文件,无论使用#include#import@import中何种方式,最终都会映射为@import方式。

Cocoapod安装的AFNetworking文件的modulemap

// 声明framework的module名称为AFNetworking
framework module AFNetworking {
  // 导入文件的集合(如果没有关键字header那么umbrella后面需要跟上头文件的文件夹名称)
  umbrella header "AFNetworking-umbrella.h"

  export * //把引入的头文件重新导出。
  module * { export * } //把导入头文件修饰成子module,并把符号全部导出(第一个通配符*表示子module名称和父module名称一致)

// 如果要指定子module的名称需要使用explicit关键字
// eg:
  explicit module NANetworking {
    header "NANetworking.h"
    export *
  }
}

umbrella:雨伞头,可以理解为伞柄。一把雨伞的伞柄下有很多伞骨,umbrella的作用是指定一个目录,这个目录即为伞柄,目录下所有.h头文件即为伞骨。

explicit:显示指明子module名称。

自定义Module

为什么需要用到自定义Module?

因为在生成一个自定义库时,在我们的Framework项目中并没有帮我们生成ModuleMap文件,它只会在编译时自动帮我们生成。这样就存在一个问题,如果我们想在项目中配置自己的东西,比如说配置一个子Module。这种场景下,我们就需要写一个自己的Module文件。

  • 写好一个Module文件后,将项目加到对应Framework项目的Tartget中。
  • 配置设置中的Module Map File,路径的设置是以SRCRoot为前置路径的。
  • 因为系统默认查找module.modulemap文件。自定义的modulemap文件,无论什么名称,在编译后,都会把文件名称改成module.modulemap文件名。
  • 子module的导出可以用通配符,也可以一个个单独指定。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容