Objective-C Swift 混编的模块二进制化 1:基础知识
栏目: Objective-C · 发布时间: 5年前
内容简介:Objective-C 与 Swift 混编在使用上主要依赖两个头文件:ProjectName-Bridging-Header.h 和 ProjectName-Swift.h。对于 Swift 调用 Objective-C,在 ProjectName-Bridging-Header.h 中 import 要使用的 Objective-C 头文件。对于 Objective-C 调用 Swift,需要编译过程中生成的 ProjectName-Swift.h 文件,此文件会将 Objective-C 需要使用的
Objective-C 与 Swift 混编在使用上主要依赖两个头文件:ProjectName-Bridging-Header.h 和 ProjectName-Swift.h。
对于 Swift 调用 Objective-C,在 ProjectName-Bridging-Header.h 中 import 要使用的 Objective-C 头文件。
对于 Objective-C 调用 Swift,需要编译过程中生成的 ProjectName-Swift.h 文件,此文件会将 Objective-C 需要使用的 Swift 类转成 Objective-C 格式的 .h 文件。下面就是一段例子:
Build Pipeline
当 Objective-C 与 Swift 进行混编时,编译的过程(Pipeline)是:
- 首先编译 Swift Module。预编译 Bridging Header 后,再编译 Swift 源文件。
- Swift 编译完成后,生成 ProjectName-Swift.h 的头文件供 Objective-C 使用。
- 最后编译 Objective-C 源文件。
Circle Reference
在开发过程中,有时候会遇到 ’ProjectName-Swift.h’ file not found
错误,而 ProjectName-Swift.h 又是在编译时动态生成的,当出现这样的错误时就比较迷惑。下面就分析一下出现该错误的原因。
@objc (TTLesson) class lesson: NSObject { } 复制代码
假设有一个 Swift 类 Lesson,Objective-C 的 TTLessonViewController 中使用了该类,因此需要引入 MixedProj-Swift.h 头文件。
// TTLessonViewController.h #import "MixedProj-Swift.h" @interface TTLessonViewController: UIViewController @property (nonatomic, strong) TTLesson *lesson; @end 复制代码
如果 TTLessonViewController 又要在 Swift 中使用,需要将 TTLessonViewController.h 加入到 MixedProj-Bridging-Header.h 中
// MixedProj-Bridging-Header.h #import "TTLessonViewController.h" 复制代码
此时编译就会出现 MixedProj-Swift.h file not found
的错误。重新回顾一下混编时的 Pipeline:
- 首先编译 Swift,需要先处理 MixedProj-Bridging-Header.h。
- 在处理 MixedProj-Bridging-Header.h 时,里面的 TTLessonViewController.h 中引用了 MixedProj-Swift.h 头文件。
- 此时由于 Swift 还没编译,因此 MixedProj-Swift.h 头文件并没有生成,所以出现 MixedProj-Swift.h 找不到的错误。
解决办法就是不显式的 import 头文件,而是使用前置声明(Forward Declaration),打破 Circle Reference。
// TTLessonViewController.h @class TTLesson; @interface TTLessonViewController: UIViewController @property (nonatomic, strong) TTLesson *lesson; @end 复制代码
语言混编的一些思考
笔者在了解 Objective-C 与 Swift 混编时一直在思考语音为什么能混编,语言到底是如何混编的?笔者了解的也不多,这里只是谈谈自己的一些思考,抛砖引玉。
对于语言混编,不妨尝试着回归到程序本质的角度进行看待:
程序 = 数据结构 + 算法 / 数据 + 逻辑
在机器码层面,所有编程语言都转化成了机器指令:
- 数据:内存访问,一个基地址 Base + 偏移 Offset,读取 Size 长度的内存。
- 逻辑:用符号标记的一段机器码,跳转到符号标记的地址。
那语言想要混编,就需要在数据和逻辑之间 建立连接 :
- 数据:要么内存布局相同,可以直接用;要么互相了解转换规则能够进行转换;要么使用通用格式进行通信。
- 逻辑:符号能够匹配上或者互相识别,方法调用的 Call Convenience 是相同的,能够互相跳转。
最后发散一下,既然是建立连接,那么通过 TCP/HTTP/IPC 等方式建立的通信可以看成更广义上的混编。例如客户端以 REST API 的方式向服务器端请求数据也可以看成客户端语言 Objective-C/Swift/Kotlin/Java 与服务器语言 Java/Go/Python 的混编,只是两端之间使用 URL(逻辑)+ JSON(数据)这种通用格式/协议的方式建立起了连接。
Clang Module
Module 是 WWDC 2013 就引进的技术,在 Xcode 的 Build Setting 中能看到 Enable Modules (C and Objective-C) 的设置项,在 OTHER_CFLAGS 中也看到了 -fmodule-map-file=“xxx.modulemap”,但是一直对其不太了解,一项技术的出现总是为了解决某些痛点,那 Module 是为了处理哪些问题呢?
Module 解决什么问题?
以前在 C/C++/Objective-C 中,源文件中引入的头文件在编译时需要进行展开,预处理宏等相关操作。这样的方式存在以下几种问题:
Header Fragile
由于头文件是一起展开后再统一处理宏,当宏重名时,头文件引入的顺序就会导致不一样的结果,并且宏只是简单粗暴的文本替换,也存在宏污染的可能。例如,AppDelegate.m 中定义了一个 readonly 的宏,与 Objective-C 中属性的关键字冲突了。
// iAd/ADBannerView.h @interface ADBannerView : UIView @property (nonatomic, readonly) ADAdtype type; @end // AppDelegate.m #define readonly 0x01 #import <iAd/iAd.h> @implementation AppDelegate // .... @end 复制代码
由于是文本替换,预处理完之后,ADBannerView 的 type 属性中,readonly 被替换成了 0x01,导致编译错误,并且很难定位到问题根源。
// AppDelegate.m #define readonly 0x01 @interface ADBannerView : UIView @property (nonatomic, 0x01) ADAdtype type; @end @implementation AppDelegate // .... @end 复制代码
Compile Time
由于每个源文件中的头文件都需要展开、预处理,假设有 M 个源文件,N 个头文件,需要 M * N 的编译时间。
为了优化这个问题,Objective-C 引入了预编译头文件(Pre-Compiled Header),将公用的头文件放入预编译头文件中预先进行编译,然后在真正编译工程时再将预先编译好的产物加入到所有待编译的 Source 中去,来加快编译速度。
但是 PCH 文件会导致里面的头文件是全局可见的,相当于变成了全局依赖,可能会导致 Namespace Pollution。并且当 PCH 文件变成庞大时,还是会导致预编译头文件的时间变长。
Link & Use
在使用框架时,需要 import 正确的头文件,不然可能导致编译问题。另外,仅仅 import 头文件是不够的,需要在设置中链接对应的库,不是很方便。
Module 是什么?
framework module UIKit { umbrella header "UIKit.h" module * {export *} link framework "UIKit" } 复制代码
Modules 相当于将框架进行了封装,看一下上面 UIKit 的 modulemap 文件,Module 在 modulemap 中定义了框架的三大核心要素:
- umbrella header 描述主要头文件:UIKit.h
- module 描述需要导出的子 Module:全部导出
- link 描述需要链接的 Framework:UIKit
在实际编译时,加入到一个用来存放已编译添加过的 Modules 列表中。如果在编译的文件引用到某个 Modules 的话,将首先在这个列表内查找,找到的话说明已经被加载过则直接使用已有的,如果没有找到,则把引用的头文件编译后加入到这个表中。
所谓 umbrella header,就是 includes all of the headers in its directory,一个包含框架内所有需要开放头文件的 Wrapper 头文件。为什么叫”雨伞“呢?笔者猜测是“雨伞”能比较形象的描述这种头文件的作用。可以想象一下雨伞☂️的形状和作用,最顶部的那个尖就像把所有东西集中起来,雨伞也将伞下的细节给遮盖起来了。
@import ModuleName; @import ModuleName.SubmoduleName; 复制代码
通过 @import
使用 Module,并且 Module 支持 Submodule。如果在 Build Settings 中将 Enable Modules (C and Objective-C) 打开,不需要改变代码,即使使用的是旧的 #import
方式,编译器也会在编译的时候自动地把可能的地方换成 Modules 的写法去编译的。
总之,Module 有如下特点:
- 单独编译处理,宏不会互相影响
- 编译时间从 M * N 变成了 M + N
- 自动链接相关库
下一篇,将讲一下如何将一个 Objective-C 与 Swift 混编的代码,从依赖混乱不完备的 Git Submodule,一步步的抽成 Development Pod,最后变成二进制的过程与遇到的坑。
Article by Joe Shang
参考:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Swift学习之与OC混编
- iOS Native混编Flutter交互实践
- Flutter 开发 (2)优雅的 Flutter 组件化 混编方案
- OC和Swift混编(一)——OC与Swift相互调用
- OC与Swift混编项目迁移到Swift4.2小记
- 从预编译的角度理解 Swift 与 Objective-C 及混编机制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
电商产品经理宝典:电商后台系统产品逻辑全解析
刘志远 / 电子工业出版社 / 2017-10-1 / 49.00元
时至今日,对于产品经理的要求趋向业务型、平台型,甚至产生了细分领域专家。纯粹的前端产品经理(页面、交互)逐渐失去竞争力。而当后台产品经理的视野开始从功能延伸到模块,再延伸到子系统,最后关注整体系统时,就有了把控平台型产品的能力。 《电商产品经理宝典:电商后台系统产品逻辑全解析》围绕“电商后台产品”,从电商的整体产品架构入手,逐步剖析各支撑子系统。通过学习电商产品后台的架构和逻辑,可以让读者从......一起来看看 《电商产品经理宝典:电商后台系统产品逻辑全解析》 这本书的介绍吧!