Module化

想直接了解modules更官方解释的可参考官方文档:
https://clang.llvm.org/docs/Modules.html#introduction

import 和 include

在了解Module化之前我们需要先了解一下OC的import机制。#import <SDWebImage/UIImage+Gif.h>
,日常开发中都写这样的代码,用来引用其他的头文件。在C和C++里是没有#import的,只有#include,用来包含头文件。#include做的事情其实就是简单的复制粘贴,将目标.h文件中的内容一字不落地拷贝到当前文件中,并替换掉这句include,而#import实质上做的事情和#include是一样的,只不过OC为了避免重复引用可能带来的编译错误(这种情况在引用关系复杂的时候很可能发生,比如B和C都引用了A,D又同时引用了B和C,这样A中定义的东西就在D中被定义了两次,重复了),而加入了#import,从而保证每个头文件只会被引用一次

import 导入

图片.png

预处理之后的AppDelegate
图片.png

include

include单次导入是和import是一样的,这里不再赘述
重点看下重复导入的情况

include重复导入的AppDelegate文件.png

import预编译之后的AppDelegate.m文件.png

使用include会把ViewController.h头文件拷贝两次,并且编译报错重复定义
图片.png

使用import 重复导入
import导入的AppDelegate.m文件.png

import预编译之后的AppDelegate.m文件.png

import = #pragma once + include
使用#pragma once修饰后可正常编译, 预编译后的文件也只copy一次

图片.png

具体实现原理是通过 #ifndef #define 一个头文件标记,在处理头文件引用的时候则判断该标记,如果已经定义过就不再对目标文件进行粘贴。

无论是includeimport本质都是对头文件的复制粘贴,这样就带来一个问题:当引用关系很复杂,或者一个头文件被非常多的实现文件引用时,编译时引用所占的代码量就会大幅上升, 因为被引用的头文件在各个地方都被copy了一遍。为了解决这个问题,C系语言引入了预编译头文件(PreCompiled Header),将公用的头文件放入预编译头文件中预先进行编译,然后在真正编译工程时再将预先编译好的产物加入到所有待编译的Source中去,来加快编译速度。比如iOS开发中的.pch文件就是一个预编译头文件,默认情况下,它引用了UIKit和Foundation两个头文件
于是理论上说,想要提高编译速度,可以把所有头文件引用都放到pch中。但是这样面临的问题是在工程中随处可用本来不应该能访问的东西,而编译器也无法准确给出错误或者警告,无形中增加了出错的可能性。

swift

Objective-C中,我们需要通过#import 或#include导入头文件,编译器在预处理阶段将这些头文件里面的API复制到当前文件,然后可以访问, swift缺少预处理阶段,头文件不能像 Objective-C一样使用#import 或#include导入了,但还是需要类似的操作才能访问到这些API。那么swift该怎么引用呢?

OC的编译过程

  1. 预处理
  2. 词法分析
  3. 语法分析
  4. 静态分析
  5. 生成汇编指令
  6. 汇编
  7. 链接

Swift编译

  1. 解析
  2. 语义分析
  3. Clang 导入器
  4. SIL 生成
  5. SIL 保证转换
  6. SIL 优化
  7. LLVM IR 生成

对于OC和Swift混编中,肯定会涉及到头文件的引用,swift文件中直接导入肯定是不行的!
苹果提供了两种方案来实现混编调用

1.桥接文件

调用 Objective-C 文件,主要通过桥接文件来实现,在桥接文件中导入需要暴露给 Swift模块的 Objective-C 类头文件,即可在 Swift模块中直接调用,如下图所示:

图片.png

调用 Objective-C 编写并打包封装的静态/动态库

不同于文件调用,Swift 调用 Objective-C 库可以通过 LLVM 的 Module系统来实现,Xcode 打包时默认支持Module 系统,它对当前开发者来说没有任何成本,只是 Framework 中自动生成 modulemap 文件(后文会介绍),我们主要通过这个描述文件来访问。

图片.png

桥接文件、Module 是目前我们实现 Swift 调用 OC 的主要方案,对Swift开发者来说,这两种方案的成本都比较低,桥接文件相当于我们的头文件汇总声明,而Module对调用方来说,基本是零成本

Modules

Apple于2012年引入了Module机制,不同于#includeModule采用了更高效的语义模型。用树形的结构化描述来取代以往的平坦式#include并缓存下来,生成一个树形结构的modulemap,用于描述框架、系统头文件、控制导出的范围、依赖关系、链接参数等等。
对于支持Module的模块,在编译时会被当做一个独立的编译单元,该单元只会被编译一次,编译器会维护一个已经编译的单元列表。如果在目标文件中引用到了某个Module,首先会在这个单元列表中进行查找,如果没有找到会进入编译流程并添加进来,如果找到了则直接使用已编译好的Module单元,类似于App中常用的缓存机制。在Module这个引用机制下,预处理消耗由M*N减低到了M+N的级别。
在实际使用中,可以将Module看作一个框架接入的中间件,这个中间件维护了编译单元和具体headers的路由关系,这种路由关系是通过modulemap这种形式来表达的。我们通过UIKit的modulemap来具体解释一下:

图片.png

图片.png
  • umbrella header
    我们应该非常熟悉<UIKit.h>这个文件,在modulemap中,它起到一个汇总的作用,我们查看UIKit.h这个文件,会发现它其实是对UIKit所有可调用头文件的汇总,这里主要是为了语法上的便利,避免在该文件中描述所有的头文件。
  • export*
    export *语义作用是描述所有子Module,即:假如调用方可能需要调用UIKit中所有的头文件,只需要声明UIKit即可.
  • explicit module
    explicit module语义是针对子Module进行的描述,它将Module根据实际需要拆分成了不同的子Module。即:调用方需要针对调用手势识别相关的内容,只声明UIGestureRecognizerSubclass这个Module即可。
import UIKit.UIGestureRecognizerSubclass 

Module 实践

在Xcode内开启Module支持选项
手动为工程开启支持module选项很简单,只需要将Target配置选项开启即可。

图片.png

Framework如何支持
创建支持Module的Framework

在Xcode中创建framework并支持Module也是很简单的: 在新建Framework的时候,Xcode默认在build settings中设置了Defines Module为YES:


图片.png

当你编译framework的时候,在对应路径下就已经生成了module文件,格式为:

图片.png

如果没有引用其他framework, xcode中默认不显示Products目录,在General->Frameworks and Libraries 中随便添加一个就可以显示了


图片.png

推荐阅读更多精彩内容