iOS oc与swift组件混编方案
最近因工程业务迭代需要实现oc与swift的工程内混编和组件混编踩到了不少的坑,且网上的博客和技术贴也没有看到一个明确的解决方案其绝大部分都是做到一半然后给一个很耐人寻味的结果,所以也是花了大量的时间去研究过程和原理在这里做一个简单的分享与记录,写的不好,大家请多多指教。
因我的工程是以组件式的形式进行迭代开发的,所以只会有一个.xcodeproj工程和多个target的形式进行业务组件的开发和剥离,目前我的每个业务组件并不是一个庞大的业务功能只需要做到每个组件能够与Unity引擎之间的通信且能够完成异步和同步方法交互即可,这样部署工程目的也主要是为了方便后期可以在一个工程之中维护多个小的组件,如果业务量庞大的SDK可参考Facebook的开源项目进行多工程部署。oc与swift的混编我先简单的分为大概四种情况
一、工程内混编
二、工程依赖组件
三、组件内部引用
四、组件外部引用
这里就简单的说一下每个混编方案具体该如何实现。
一、工程内混编
首先在工程内混编的话是大家经常遇到的一种情况,一般情况在oc工程中第一次创建swift文件,xcode会自动创建与swift的桥接文件ProjectName-Bridging-Header.h
这个文件创建出来后即为swift调用oc文件的header文件,可以将工程内用到的所有oc文件引入其中,编译器则会将引入的所有oc文件进行module化,即在swift的角度而言,引入的每个oc文件即为引入的每个module,这样做的目的也主要是为了提高编译速度,在编译的过程中,编译过的module会放入一个编译好的modules数据结构中,当有重复的module引入时,dyld会将先前编译好的module进行link产生最终的mach-o文件。
如果要在oc中引入swift文件的话,需要引入在编译时才能决议的文件可在Targets -> Build Settings中看到该文件名ProjectName-Swift.h
该文件会在编译的过程中产生,其主要的目的是为了将swift转译为oc,中间会有一层hash加密用于防止在LLVM dyld进行link时出现符号表冲突。其中的内容可在编译一次后看到,该文件中声明了工程内引入的所有swift文件,其中编译器在其中的实现过程就不一一阐述了。
二、工程依赖组件
在需要用到swift组件的地方引入ProjectName-Swift.h文件即可
三、组件内部引用
3.1 swift调用oc
在同一个.framework或者.a中实现swift调用oc,是无法通过配置Bridging-Header的方式彻底解决,最终会在用到组件的地方编译时会出现
看了官方文档后才知道,如果在同一个framework中swift想要调用oc的话,需要将该文件引入其umbrella-header.h中,还需要将该target的DEFINES_MODULE配置改为YES即可
3.2 oc调用swift
只需要将TargetName-Swift.h文件引入即可,但这时候需要增加命名空间<TargetName/TargetName-Swift.h>
四、组件外部引用
在组件外部调用的实现过程中,我的工程是纯oc部署的工程环境,每个组件是通过反射进行的初始化,在实现的swift组件的时候,无法通过oc的反射方法到纯swift的类中(在编译的过程中swift的类有一层hash加密),所以我做了一层oc的wrapper,让组件能够通过oc反射初始化后内部再调用swift
4.1 swift调用oc
在我的工程环境中,会有多个基础组件会在组件之间产生依赖,同级组件之间可通过基础组件实现的中间件进行通信。
所以用到的外部组件需要在umbrella-header.h 中将其引入,如果想要公开非组件内部的Target需要在Target -> Build Phases -> Headers里面拖拽到Public目录下,如果不想公开则需要拖拽到Project目录下,拖到Private目录下的文件会在编译时产生一个PrivateHeaders目录到framework中。具体的区别可参考官方文档。
4.2 oc调用swift
同3.1实现方案一样引入即可。
Tips:
在开发的过程中可能会遇到如下问题,做个简单的解决记录
1.在umbrella-header.h 中引入了非组件内部的oc文件会出现以下警告
Umbrella header for module 'xxx' does not include header 'xxxr.h'是正常现象,因为module化的原因,在每个module之间不应该产生引入或者依赖。我这里也暂时没有解决这个警告,看官方文档上说需要手动配置modulemap文件及引入关系,我尝试了手动配置了却依然不行,有会编写modulemap文件的大佬还请说说怎么搞,先在这磕头了。
2.在我的工程环境中编译会在编译前3次出现could not build module "xxx",第4次才会正常的编译成功,这也是最离谱的错误,后来才发现Xcode在编译的过程中是默认开启了Parallelize Build,会导致在编译swift target的时候无法确定引入关系导致编译的不确定性(如果有编译缓存,则每次都会成功)
这是加速编译的配置,通过关闭便可以临时解决问题让每次编译成功,但在Xcode13上苹果强烈不建议关闭这个开关,并且更换了title,更加贴切的指明了编译时是依赖顺序或手动顺序
所以需要手动配置framework的依赖顺序后,确保在编译过程中能先编译组件依赖的framework后再编译组件,即可正常的完成编译。
3.在发布组件的时候需要在podspec文件中添加s.swift_version = "5.0"描述,且可能还会遇到一个很离谱的问题就是在高版本Xcode中build的Swift Framework在低版本Xcode中无法正常使用,会出现符号缺失或者符号未找到等错误。具体的原因是因为每个Framework中嵌入的Swift Module 不一样,不同版本之间的Swift Module苹果并没有做兼容。所以目前的方案只能在版本相近的Xcode中,才能使用打出来的Framework。
4.因为SDK工程需要进行加固,处理包体膨胀要对其进行优化需要开启bitcode 功能又是一堆坑就以后有时间再写。