Swift:巧用module.modulemap,告别Bridging-Header.h

6,691 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

前言 项目背景

项目里面有这么一个需求,在一个App项目中创建多个Static Library,各司其职进行模块与职责划分。

别问为啥没有使用私有库Cocopods进行,反正目前就是为了方便后续各个Static Library,可以随便拖动到其他项目中进行复用。

然后,问题来了。

问题:在Static Library无法引用友盟的framework

为了便于说明与演示,我特别创建了一个Demo,通过截图进行讲解。

我有个项目叫做TestUM,里面包含一个SomeSDK,我希望在SomeSDK里面,包含高德地图和友盟统计的功能。

image.png

于是乎,我在Podfile文件中进行了配置:

target 'SomeSDK' do

  # Comment the next line if you don't want to use dynamic frameworks

  use_frameworks!

  pod 'AMapSearch', '= 8.1.0'
  pod 'AMapLocation', '= 2.8.0'

  pod 'UMCommon', '~> 1.3.4.P'
  pod 'UMSPM'
  pod 'UMCCommonLog'

end

target 'TestUM' do

  # Comment the next line if you don't want to use dynamic frameworks

  use_frameworks!

  # Pods for TestUM
end

注意,进行Pod的target是SomeSDK而非TestUM但是实际上TestUM也是能引用高德与友盟的库。

最后,根据友盟集成的文件,需要添加桥接文件进行处理:

image.png

在TestUM下,我通过import AMapFoundationKit,我们可以顺利的调用高德的相关API,因为桥接了友盟,我也可以顺利的调用友盟的相关API:

image.png

然而,在SomeSDK下,因为可以import AMapFoundationKit,我依旧可以调用高德,但是友盟却怎么也点不出来了:

image.png

我尝试在SomeSDK也创建一个类似主工程中Bridging-Header.h的文件,对友盟进行桥接,然而得到的却是编译错误using bridging headers with framework targets is unsupported

不支持,这条路被堵死了。

如果桥接行不通,SomeSDK就无法使用友盟统计的功能,只能将其相关业务移植到主工程去,这明显不符合公司要求。

4f9dd5d4aba06d291d7b1e4d05683724.jpeg

领导就一句话:高德可以,友盟为什么不行?

image.png

现在回头看看,为何高德地图的既可以在TestUM又可以在SomeSDK中进行引用——因为它能在工程中的*.swift文件中进行import

而友盟在通过TestUM-Bridging-Header.h文件进行桥接后,在TestUM主工程的.swift文件中,无需import,直接调用即可,但是在SomeSDK的子工程中无法调用。

高德与友盟的架包到底有何差异?🤔🤔🤔

AMapFoundationKit.framework与UMCommon.framework对比

其实高德与友盟的Pod引用还是非常相似的,因为都是封装的静态库,Pod集成的都是非开源的.framework架包。

这里我们将AMapFoundationKit.framework与UMCommon.framework做一下对比:

高德友盟
image.pngimage.png
Snip20220905_6.pngSnip20220905_6.png
image.pngSnip20220824_21.png
  1. 通过Xcode展开工程看,Pod中,AMapFoundationKit.framework不仅展示了Frameworks文件夹,同时暴露的.h文件也显示了,而UMCommon.framework没有显示.h文件。
  2. 通过AMapFoundationKit.podspec.jsonUMCommon.podspec.json,我们会发现虽然两者都是.framework的pod集成方式,但是在配置参数的差异方式决定了显示不同。
  3. 看.framework的文件结构,很明显的发现AMapFoundationKit.frameworkUMCommon.framework多一个Module文件夹!

就让我们看看,这个Module文件夹下面吧。

里面就只有一个module.modulemap文件,里面长这样:

image.png

关于umbrella header大家可以看看参考文档What is an umbrella header?,它的功能就是将AMapFoundationKit.h里面暴露的.h文件,通过循环都暴露出来。

AMapFoundationKit.h里面长这样: image.png

回想一下,我们可以在*.swift文件中可以import AMapFoundationKit是不是因为有module.modulemap中的配置缘故?

带着这个问题,我去搜索了一下module.modulemap的相关资料。

在一篇文章中我找到相关的信息与灵感:

image.png

As Bridging-Header can help us in App Target and App Test Targetnot in static library or dynamic libraries to use the Objective C / C APIs into Swift classes, modulemap can help us here.

通过理解,Pod这种.framework的静态库,在主工程的应用可以通过桥接解决,而在主工程的的static library则需要通过modulemap来进行解决。

为UMCommon.framework手搓一个module.modulemap

本着死马当活马医的想法,我想为UMCommon.framework手搓一个module.modulemap

首先我特地看了一下UMCommon.framework中Headers里面的文件:

image.png

抱着试一试的态度,我新建了Modules一个文件夹,并写了这样一个文件,注意我并没添加所有的.h文件,只是为了方便测试。

framework module UMCommon {

   header "MobClick.h"

   header "UMConfigure.h"

   header "UMCommon.h"
   
   export *

}

然后将其放到对应的UMCommon.framework。

image.png

见证结果的时刻来了,编译,试着import,成功了!

image.png

我们甚至可以,点击看看这个import UMCommon

image.png

MobClick类已经完美通过Swift表示了。

而且此时,我们可以把主工程里面的Bridging-Header.h里面桥接文件注释掉(甚至将这个.h文件删除),在*.swiftimport对应的类,即可成功引入与调用!

总结

  • 将Pod中的某些需要桥接的库,通过手搓一个module.modulemap,我们完全有能力抹去桥接操作,但是同时这样有一个问题,一旦Pod的库,升级或者文件进行了变更,自行写的module.modulemap可能也需要更改。

    而且更改Pod下的库的文件,也不太符合操作规则。

    另外,大家可以尝试把AlipaySDK.framework通过这种方式去除桥接试试,原理都是一样的,就当练手。

  • 还有一种方式就是自己创建一个私有的Spec,自己添加module.modulemap后,进行pod库管理,但是这样还是避免不了上游更新,私有库也要同步更新的问题。

最好的Pod集成方式,就像高德的库,官方将podspec配置好,使用者直接傻瓜pod install就好了。

参考文档

What is an umbrella header?

Swift Objective C interoperability, Static Libraries, Modulemap etc…

自己写的项目,欢迎大家star⭐️

RxStudy:RxSwift/RxCocoa框架,MVVM模式编写wanandroid客户端。

GetXStudy:使用GetX,重构了Flutter wanandroid客户端。