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

語言: CN / TW / HK

我報名參加金石計劃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.png | image.png | | Snip20220905_6.png | Snip20220905_6.png | | image.png | Snip20220824_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客戶端。