背景:
目前所在的項目組是多媒體開發(fā)組,項目開發(fā)的場景包括了播放器,特效,視頻編輯,視頻模板,代碼規(guī)范工具等多個場景,隨著開發(fā)迭代,項目已經(jīng)變得越來越龐大,所有代碼放在一個工程中不利于團隊開發(fā)和維護,于是決定將項目根據(jù)功能拆分成不同的庫(模塊化),各自維護。
iOS平臺中使用了cocoapods這個管理工具方便進行庫的管理,使用它自制私有庫的關(guān)鍵需要自己編寫podspec文件(描述庫的特定版本信息的文件),于是各種史詩級災(zāi)難開始了。
以下是各種災(zāi)難現(xiàn)場
作為一個剛才深坑里面爬出來的人,趁著還有一口氣在,趕緊記錄下這次爬坑經(jīng)歷,給使用podspec的同學(xué)做一個參考~
問題解析
問題一
首先我們來看圖一,這里報的錯誤是<cassert>頭文件找不到。
<cassert>是c++標(biāo)準(zhǔn)庫里面的一個系統(tǒng)頭文件,一般出現(xiàn)這種情況的原因是在Objective-C類型文件中引用了C++的頭文件導(dǎo)致的,如果要引用C++的頭文件必須將后綴名改成(.mm)即將文件類型改為Objective-C++類型。
然而,目前的問題場景并不是這樣的,我們并沒有在Objective-C文件中引用c++頭文件。目前我們用到了庫有兩個,一個是渲染庫(illusion)和一個播放器庫(GDMMCore)。illusion庫中有c++文件和Objetcive-C++文件(即封裝c++,給外部使用的中間件)和Objective-C文件,GDMMCore庫依賴于這個illusion庫(即GDMMCore.dependency 'illusion'),我們只有在GDMMCore的Objective-C文件中引用了illusion庫的Objective-C文件,如下圖所示,就造成了圖一的錯誤。這個是為什么呢?
我們沿著引用路徑查看,最后定位到了一個叫做illusion-umbrella.h的文件中。
如圖所示,我們明明只有引用GDCoreGraphics.h(Objective-C類型)這個頭文件,為什么引用路徑會定位到GDInOutAnimation.h(c++頭文件,這個文件里面間接的引用的報錯的<cassert>這個頭文件)呢?這個illusion-umbrella.h到底又是個什么東西呢?
首先,這個illusion-umbrella.h是因為我們在Podfile中使用了use_frameworks!后由cocopods幫我們自動生成的頭文件,這里面導(dǎo)入了所有我們在podspec中配置的共有頭文件,有點類似于pch文件的作用,如果不使用use_frameworks!則不會有這個文件。然后這里有個坑,就是只要我們引用了這個文件中導(dǎo)入的頭文件(無論是什么類型的頭文件),就會引用整個illusion模塊,即illusion-umbrella.h中所有的頭文件,所以這就造成了上面的引用路徑。
這里提到了use_framework!,那么使用和不使用這個有什么區(qū)別呢?
上面兩幅圖分別是使用了use_framework!和沒有使用use_framework!的結(jié)果,首先正如我們上面說到的使用了use_framework!之后會生成一個xx.umbrella.h文件。其次,使用use_framework之后最終組件會編譯成.framework類型的文件,而不使用的話則是.a類型的文件形式。當(dāng)然,還不只有這些區(qū)別哦~
它還會導(dǎo)致最終的頭文件路徑不一樣,下面兩幅圖分別是使用了use_framework!和沒有使用最終頭文件的路徑。
那么這個信息有什么用呢?接下來我就介紹一下我是怎么解決第一個問題的,正如上面提到的,問題在于引用了xx.umbrella.h中的文件就會引用整個umbrella.h中的所有頭文件(這些頭文件中包含了c++文件)。
第一個思路,不使用use_framework!就不會生成umbrella.h這個文件,也就不會導(dǎo)致引用整個模塊的頭文件,也就不會有圖一的問題了。但是在Podfile中使不使用use_framework!是由業(yè)務(wù)方(即需要用這個庫的同學(xué))自己決定的,我們作為服務(wù)方(即提供組件的同學(xué))當(dāng)然不能限制使用的同學(xué)要這樣做,畢竟我們的目標(biāo)是要讓業(yè)務(wù)方同學(xué)用起來方便,爽。而這種方案是對業(yè)務(wù)方同學(xué)做了一個很大的限制,方案pass。
第二個思路,上面我們說到了umbrella.h里面引入的是所有的我們在podspec中配置的公開(public)的頭文件,也就是說私有的(private)頭文件不會出現(xiàn)在這里面,那么我們可以將c++文件配置成私有的頭文件,這樣的話即使引用了整個umbrella.h里面的頭文件也不會引用到c++頭文件,方案可行,配置如下圖所示。
綜上,方案二能夠解決圖一引用c++文件的問題,但是以方案二執(zhí)行之后又導(dǎo)致了另一個問題,就是由于把文件設(shè)置成了私有導(dǎo)致了GDMMCore庫中找不到c++文件,無法使用,這個問題又要怎么處理呢?
這個我們可以通過在Build Setting中設(shè)置Header Search Paths,這樣編譯器就能根據(jù)我們配置的路徑去找到私有的頭文件了,如下圖所示。
那么頭文件的路徑如何設(shè)置呢?又該如何在podspec中配置這些呢?讓我們回到podspec中,看看具體的配置代碼:
search_paths = [
#Podfile不使用use_frameworks搜索路徑
'$(PODS_ROOT)/Headers/Public/illusion',
'$(PODS_ROOT)/Headers/Private/illusion',
#Podfile使用use_frameworks庫內(nèi)搜索路徑
'$(PODS_ROOT)/illusion/Headers',
'$(PODS_ROOT)/illusion/PrivateHeaders',
#Podfile使用指定路徑鏈接
'$(PODS_TARGET_SRCROOT)/src',
'$(PODS_TARGET_SRCROOT)/src/animation/inout',
'$(PODS_TARGET_SRCROOT)/src/animation/transition',
'$(PODS_TARGET_SRCROOT)/src/entity',
'$(PODS_TARGET_SRCROOT)/src/filter',
'$(PODS_TARGET_SRCROOT)/src/math',
'$(PODS_TARGET_SRCROOT)/src/stb',
'$(PODS_TARGET_SRCROOT)/src/util',
]
private_header_path = [
'${PODS_CONFIGURATION_BUILD_DIR}/illusion/illusion.framework/PrivateHeaders',
'$(PODS_ROOT)/Headers/Private/illusion',
]
s.pod_target_xcconfig = {
'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' ,
'HEADER_SEARCH_PATHS' => search_paths.join(' '),
}
s.user_target_xcconfig = {
'HEADER_SEARCH_PATHS' => private_header_path.join(' '),
}
代碼中我們可以看到一個關(guān)鍵key值 'HEADER_SEARCH_PATHS',這個對應(yīng)的就是頭文件搜索路徑了。我們可以看到代碼中有兩個地方都進行配置了,分別是pod_target_xcconfig和user_target_xcconfig,那么這兩個有什么區(qū)別呢?
pod_target_xcconfig設(shè)置的是當(dāng)前庫的Build Settings,這里對應(yīng)的是illusion庫。user_target_xcconfig設(shè)置的是project中的Build Settings,即在運行的target中的Build Settings。這個在Cocoapods的官網(wǎng)上不建議使用,上面說設(shè)置這個有可能會和工程中本身的設(shè)置造成沖突。這里因為我們這個庫有可能被工程中直接使用,c++頭文件被設(shè)置為私有頭文件,所以這里進行了配置。
接下來我們看下頭文件的路徑,如代碼所示,分為三種情況:
1.使用了use_frameworks,此時的頭文件在framwork中尋找,公有的頭文件在$(PODS_ROOT)/Headers/Public/庫名中,私有的頭文件在對應(yīng)的Private/庫名中。
2.不使用use_frameworks,此時的頭文件在Pods文件夾對應(yīng)庫中的Headers(公有)和PrivateHeaders(私有)文件夾中。
3.還有一種情況是在Podfile中是使用本地庫的方式引用,即通過path = > '../'的方式,此時的頭文件路徑會到本地工程中對應(yīng)的路徑中尋找。
根據(jù)以上不走配置好podspec之后,我們執(zhí)行pod install,再次編譯發(fā)現(xiàn)引用c++頭文件的問題解決了。
問題二
這里報的錯誤是頭文件找不到,我們發(fā)現(xiàn)這里引用頭文件的方式是通過相對路徑去尋找的,使用相對路徑的前提是必須能夠找到當(dāng)前所在的目錄層級,于是還是像上面一樣,我們需要把當(dāng)前的目錄層級配置到HEADER_SEARCH_PATHS中。
search_paths = [
#Podfile使用指定路徑鏈接
'$(PODS_TARGET_SRCROOT)/source',
'$(PODS_TARGET_SRCROOT)/source/common',
'$(PODS_TARGET_SRCROOT)/source/module/media',
]
s.pod_target_xcconfig = {
'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES',
'HEADER_SEARCH_PATHS' => search_paths.join(' '),
}
再次pod install之后這個問題也解決了。
問題三
圖中的問題信息說的是GDSizeFormatToVideo這個函數(shù)在兩個模塊中有不同的定義,但是搜索了整個工程中對于這個函數(shù)的定義只存在于illusion庫中的GDCoreGraphics.h中,我們找到報錯的引用路徑看看
如果所示,我們的引用方式用的是<GDMMCore/GDTimelineInfo.h>的方式找到GDTimelineInfo.h,而GDTimelineInfo.h中間接的引用到了GDCoreGraphics.h這個頭文件,這里有一個坑就是用這種方式引用的話,編譯器會認為這里間接引用到的GDCoreGraphics.h這個文件是屬于GDMMCore中的文件,所以這樣就造成了編譯器認為在GDMMCore和ilusion兩個庫中都存在這個GDCoreGraphics.h頭文件,以及頭文件中的函數(shù),也就造成了圖中所示的錯誤。
這個問題的解決方式比較簡單,我們只需要更改引用文件的方式,不使用<模塊/文件>的方式引用,而是直接使用"文件"的方式,這樣就解決了這個問題。
以上就是我這次的podspec踩坑經(jīng)歷,如果有遇到類似問題的同學(xué)可以參考一下,也可以和我交流哈~
參考鏈接: