工程swift与OC混编改造

最近公司项目准备引入swift,由于目前工程已经完成了组件化不再是简单的单仓工程,所以需要进行混编改造。下面记录一下自己对工程进行混编改造的思考以及过程。

混编原理

看了很多文档,比较少有讲混编原理的,这里简单介绍一下语言进行混编的基本逻辑,因为我们都知道swift和OC可以混编,但是他们为啥能混编呢?

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.002.png

首先简单的过一下编译以及静态链接的流程:

  1. 预编译阶段,B.m将引入A.h以及B.h全部copy到B.m(这种预编译方式我们称它为文本模型)

  2. 编译阶段,B.m将B自身的方法地址全部编入符号表,但是编译的过程会碰到对A.function的调用,这个时候编译器检查A.function是否有声明,明确有(跟着A.h头文件copy过来的),但是并没有相应的实现,因此会将A.function的引用做一个特殊标记,并在重定位表中记录,等待静态链接成功后修改为指向A.function真实所在的地址

  3. 完成所有.o文件的符号表的合并,根据重定位表中的记录需要重新修改符号的地址,完成真实的A.function地址的特殊标记替换。

整个过程中只要生成的A.o以及B.o中间文件是按照编译器预期的格式组织的,那么静态链接器ld的就能根据格式缝合成最终产物:可运行的app。因此无论A和B是用什么语言编写的,只要编译器B本身能够识别出对应的语言A的声明,那么就可以在B的编译过程中标记A.function,并产出规定格式的.o文件。最终在静态链接器的帮助下完成对A.function的调用。(由上也可以看出头文件其实仅仅承担了符号标记的作用,其余的编译部分都是在实现文件中完成的)
Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.003.png

以上就是语言混编的基础。

备注:JAVA的编译逻辑稍有不同,这里只讨论java转class,不讨论class通过虚拟机变成真实地址的过程,java不像c系语言一样由链接器寻找最终符号地址,他们是由写代码的人直接使用全限定词指定符号在哪个文件,因此java编译成class以后符号地址就确定了,这也是java不需要头文件的逻辑,java与kotlin混编只需要保证编译出来的字节码标准统一即可混编,因为编译某个文件对其他文件有引用,编译器会立刻对引用文件进行编译生成class,由于java和kotlin都可以识别出字节码的规则,因此可以识别出相应的方法调用从而完成混编

swift与OC混编

了解了语言混编的基础之后我们再来落实到具体的语言:swift与OC的混编。

1. swift调用OC

swift的编译器swiftc包含了clang编译器的大量的功能,因此swift调用OC,其实就是依赖于swiftc将OC文件的声明转成swift的声明(.swiftiinterface),然后swift直接使用swift语法的OC方法声明,完成了对OC方法实现的调用。如下图所示(都是xcode自动生成的)

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.004.jpeg

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.005.jpeg

2. OC调用swift

因为历史原因,OC的编译器clang是无法识别swift语言的,因此要想让OC可以识别swift的声明需要依赖于swiftc编译器将swift声明转成OC声明(FZCache-swift.h),然后OC直接使用OC语法的swift方法声明,完成对swift方法实现的调用。

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.006.jpeg

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.007.jpeg

不管是swift声明转OC声明,还是OC声明转swift声明,以上两个过程中都暗含了一个关键的因素:头文件的查找,只有找到相应的声明转换后的头文件的位置,才能访问到方法声明。才能最终完成混编。

头文件查找

1. 基于文本模型的头文件查找

就是我们常规使用的方案#import头文件,跟#include本身区别不大,除了会自动去重,但是他们处理头文件的逻辑是一样的,每次编译一个.m文件都要重新对此.m文件中的头文件引入进行复制粘贴,因此理论上时间复杂度为O(m*n),另外由于采用的是复制粘贴替换的逻辑,因此在处理一些宏定义的时候容易出错,比如可能会存在某个定义

#define nonatomic @"nonatomic"

这个宏定义可能在某个文件中是没有任何问题的,但是如果有人使用了@property (nonatomic)这样的属性的时候就会导致代码出现错误,关键由于预编译采用的是复制的方式,即便是编译器再次报错,也会让间接引入了这个宏定义声明的开发者一下子难以查找到真正的问题位置

2. clang module

基于以上的问题,苹果提出了clang module的头文件查找方案,该方案声明了一种特定的文件组织形式,以静态库为例,静态库分成两种.a和framework,clang module规定静态库必须以如下方式进行资源的组织

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.008.png

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.009.png

module使用以下方案对头文件进行访问:

@import FZCache.FZKVCache

当编译器读取到FZCache的时候就会从特定存储路径中查看是否存在有FZCache这个组件空间(也就是这里说的module),然后查询其中是否有FZKVCache的缓存产物,如果有则直接引用,如果没有就先找到FZCache.framework这个文件夹,然后进入Headers文件夹查询FZKVCache.h头文件,如果可以找到,再进入Modules文件查看是否有modulemap文件,如果有则启用module,在特定存储路径中创建一份单独的编译空间用于存放预编译缓存,否则报错,确认有modulemap文件后继续查看此描述文件中是否包含了FZKVCache.h,

framework module FZCache {
  umbrella header "FZCache-umbrella.h"

  export *
  module * { export * }
}

module FZCache.Swift {
  header "FZCache-Swift.h"
  requires objc
}
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

#import "FZCache.h"
#import "FZKVCache.h"

FOUNDATION_EXPORT double FZCacheVersionNumber;
FOUNDATION_EXPORT const unsigned char FZCacheVersionString[];

显然FZCache-umbrella.h头文件中是包含FZKVCache.h的,因此编译生成FZKVCache的预编译产物放入FZCache module空间中,以备下次使用。

所以启用了clang module以后,组件只需编译一次,从理论上极大的降低了编译时间。

swiftmodule可以认为是clang module的升级版,基本上逻辑大同小异,但是针对swift的有一些特定的优化,我们可以简单的把swiftmodule和modulemap对应起来。在swiftmodule文件中存储了对整个模块以及模块内部子模块的二进制描述。
Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.010.png

由于swiftc编译器只能通过clang module和swiftmodule识别到framework的头文件(如果是本target内部的头文件其实swiftc是能直接识别出来的,比如在主仓中swift通过桥接文件写入#import oc的头文件就可以识别到这些头文件),因此如果需要在swift仓库中引入OC仓库,就必须要对OC仓库进行clang module化。

备注:xcode对#import <A/A.h>做了优化,如果确认能找到modulemap文件,则启用clang module编译,转成@import A.A,如果未能找到则转成我们普通的#include <A/A.h>文本复制替换操作

鉴于目前所有的仓库都有可能需要使用混编,因此需要对所有的组件进行clang module化。

实现方案

我们将基于cocoapods完成所有仓库的module化。开启方法有多种。

1.use_framework!

2.use_modular_headers

3.自己写脚本生成modulemap,并组织好头文件。

这里我们选用use_framework!选项,即在开启所有仓库module化的同时,将生成产物从.a转变为framework.碰到头文件报错的位置就修改引用方式解决问题。要注意的是module化具有传递性,如果A开启了module,但是A依赖的B没有开启module,编译器就会报错。

使用方式

子仓的互相调用模式:

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.011.png

主工程内部调用方式

Aspose.Words.bb029009-75e5-4517-a727-0bf2a92b9c56.012.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/25798.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot+springsecurity+jwt+elementui图书管理系统

​​图书管理系统​​ 关注公号&#xff1a;java大师&#xff0c;回复“图书”&#xff0c;获取源码 一、springboot后台 1、mybatis-plus整合 1.1添加pom.xml <!--mp逆向工程 --><dependency><groupId>org.projectlombok</groupId><artifactId&…

子串--子字符串 0528

210102 201012 A1A2…An An…A2A1 如何做&#xff0c; 翻转的是21&#xff0c;因为2>1; 翻转的是210&#xff0c;因为2>0; 翻转的是2101&#xff0c;因为2>1&#xff1b; 翻转的是21010&#xff0c;因为2>0&#xff1b; 翻转的是210102&#xff0c;因为22且1&…

2023-05-29 用 fltk gui库编写一个打字练习程序

用 fltk gui库编写一个打字练习程序 前言一、FLTK GUI 库二、使用步骤1.引入库2.使用代码 总结 前言 给孩子练习键盘打字, 发现终端还是欠点意思, 研究了一下gui, 最终用 fltk库弄了一个. 对于没有接触过gui的人, 发现, 编程的逻辑和终端区别很大, 很繁琐, 可能需要适应适应,…

0基础学习VR全景平台篇第32章:场景功能-嵌入视频

大家好&#xff0c;欢迎观看蛙色VR官方系列——后台使用课程&#xff01; 一、本功能将用在哪里&#xff1f; 嵌入功能可对VR全景作品嵌入【图片】【视频】【文字】【标尺】四种不同类型内容&#xff1b; 本次主要带来视频类型的介绍&#xff0c;通过嵌入视频功能&#xff0c;…

Go语言并发

Go语言并发学习目标 出色的并发性是Go语言的特色之一 • 理解并发与并行• 理解进程和线程• 掌握Go语言中的Goroutine和channel• 掌握select分支语句• 掌握sync包的应用 并发与并行 并发与并行的概念这里不再赘述, 可以看看之前java版写的并发实践; 进程和线程 程序、进程…

腾讯云服务器常用端口号大全以及端口开启方法

腾讯云服务器常用端口号如80、21、22、8080等端口&#xff0c;出于安全考虑一些常用端口默认是关闭的&#xff0c;腾讯云服务器端口如何打开呢&#xff1f;云服务器CVM在安全组中开启端口&#xff0c;轻量应用服务器在防火墙中可以打开端口&#xff0c;腾讯云百科来详细说下腾讯…

在云服务器上部署简单的聊天机器人网站(源1.0接口版)

诸神缄默不语-个人CSDN博文目录 又不是不能用.jpg http://47.113.197.198:8000/chat 集成代码可参考&#xff1a;花月与剑/scholar_ease 之所以先用源1.0&#xff0c;一是因为我API都申请了&#xff0c;不用白不用&#xff1b;二是因为源1.0可以直接用国内网络连接&#xf…

Vue登录界面精美模板分享

文章目录 &#x1f412;个人主页&#x1f3c5;Vue项目常用组件模板仓库&#x1f4d6;前言&#xff1a;&#x1f380;源码如下&#xff1a; &#x1f412;个人主页 &#x1f3c5;Vue项目常用组件模板仓库 &#x1f4d6;前言&#xff1a; 本篇博客主要提供vue组件之登陆组件源码…

idea连接Linux服务器

一、 介绍 配置idea的ssh会话和sftp可以实现对linux远程服务器的访问和文件上传下载&#xff0c;是替代Xshell的理想方式。这样我们就能在idea里面编写文件并轻松的将文件上传到linux服务器中。而且还能远程编辑linux服务器上的文件。掌握并熟练使用&#xff0c;能够大大提高我…

操作系统复习2.4.0-死锁详解

什么是死锁 各进程互相竞争对手里的资源&#xff0c;导致各进程都阻塞&#xff0c;都无法向前推进 死锁、饥饿、死循环的区别 死锁&#xff1a;各进程互相持有对方想要的资源且不释放&#xff0c;导致各进程阻塞&#xff0c;无法向前推进 饥饿&#xff1a;由于长期得不到想要…

四站精彩回顾 | Fortinet Accelerate 2023·中国区巡展火热进行中

Fortinet Accelerate 2023中国区巡展 上周&#xff0c;Fortinet Accelerate 2023中国区巡展分别走过青岛、南京、长沙、合肥四站&#xff0c;Fortinet携手太平洋电信、亚马逊云科技、中企通信等云、网、安合作伙伴&#xff0c;与各行业典型代表客户&#xff0c;就网安融合、网…

电动葫芦无法运转怎么办?

有关电动葫芦无法起动与运转故障&#xff0c;电动葫芦无法起动怎么办&#xff0c;有没有好的解决办法&#xff0c;检查电源熔丝是否烧断&#xff0c;定子绕组相间短路、接地或断路&#xff0c;以及是否负载过大或传动机械故障等。 电动葫芦无法运转故障怎么办 1、首先&#xf…

C语言基础习题讲解

C语言基础习题讲解 运算符判断简单循环 运算符 1. 设计一个程序, 输入三位数a, 分别输出个,十,百位. (0<a<1000) 样例输入: 251 样例输出: 2 5 1 #include <stdio.h> int main() {int input 0;int x 0;int y 0;int z 0;scanf("%d", &input);x …

7 种常见的路由协议

网络路由是网络通信的重要组成部分&#xff0c;通过互联网将信息从源地址移动到目的地的过程。路由发生在 OSI 模型的第 3 层&#xff08;网络层&#xff09;。实际网络中通常会将静态和动态路由结合使用。静态路由适用于小型网络&#xff0c;而动态路由适用于大型网络。 什么…

界面控件DevExpress ASP.NET新主题——Office 365暗黑主题的应用

DevExpress ASP.NET Web Forms Controls拥有针对Web表单&#xff08;包括报表&#xff09;的110种UI控件&#xff0c;DevExpress ASP.NET MVC Extensions是服务器端MVC扩展或客户端控件&#xff0c;由轻量级JavaScript小部件提供支持的70个高性能DevExpress ASP.NET Core Contr…

华为路由器 IPSec VPN 配置

需求&#xff1a; 通过 IPSecVPN 实现上海与成都内网互通 拓扑图如下&#xff1a; 一、首先完成网络配置 1、R1 路由器设置 <Huawei>sys [Huawei]sys R1 [R1]un in en# 开启DHCP [R1]dhcp enable# 设置内网接口 [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip addr 10.…

Git日常使用技巧 - 笔记

Git日常使用技巧 - 笔记 Git是目前世界上最先进的分布式版本控制系统 学习资料 廖雪峰 学习视频 https://www.bilibili.com/video/BV1pX4y1S7Dq/?spm_id_from333.337.search-card.all.click&vd_source2ac127043ccd79c92d5b966fd4a54cd7 Git 命令在线练习工具 https://l…

国内可用 ChatGPT 网页版

前言 ChatGPT持续火热&#xff0c;然鹅国内很多人还是不会使用。 2023年6月1日消息&#xff0c;ChatGPT 聊天机器人可以根据用户的输入生成各种各样的文本&#xff0c;包括代码。但是&#xff0c;加拿大魁北克大学的四位研究人员发现&#xff0c;ChatGPT 生成的代码往往存在严…

项目开发-依赖倒置、里式替换、接口隔离的应用深入理解

文章目录 前言依赖倒置定义不符合依赖倒置原则是什么样子&#x1f604;完善 里式替换定义具体应用 接口隔离定义具体应用 前言 最近在做.net项目和学习这个设计模式中的依赖倒置和工厂方法&#xff0c;这个过程当中发现在开发这个.net项目中有很多不合理的地方&#xff0c;就是…

cplex基础入门(一)

这边文章会以纯新手小白的视角&#xff0c;教会大家如何快速的搭建自己的cplex模型&#xff0c;做到求解模型不求人。 目录 一、引言 1、掌握数据类型及数据结构 2、常规Cplex编程方法 3、Cplex编程步骤 4、cplex 程序框架 5、创建模型 二、规划建模的入门求解案例 1、…