Objective-C Swift 混编的模块二进制化 1:基础知识

栏目: Objective-C · 发布时间: 4年前

内容简介: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 文件。下面就是一段例子:

Objective-C Swift 混编的模块二进制化 1:基础知识

Build Pipeline

Objective-C Swift 混编的模块二进制化 1:基础知识

当 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 长度的内存。
  • 逻辑:用符号标记的一段机器码,跳转到符号标记的地址。
Objective-C Swift 混编的模块二进制化 1:基础知识

那语言想要混编,就需要在数据和逻辑之间 建立连接

  • 数据:要么内存布局相同,可以直接用;要么互相了解转换规则能够进行转换;要么使用通用格式进行通信。
  • 逻辑:符号能够匹配上或者互相识别,方法调用的 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

参考:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Visual Thinking

Visual Thinking

Colin Ware / Morgan Kaufmann / 2008-4-18 / USD 49.95

Increasingly, designers need to present information in ways that aid their audiences thinking process. Fortunately, results from the relatively new science of human visual perception provide valuable ......一起来看看 《Visual Thinking》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具