前言:最近把Xcode升級(jí)到了Xcode13,發(fā)現(xiàn)老項(xiàng)目突然運(yùn)行不起來了肩榕,原來是老項(xiàng)目使用的還是老的構(gòu)建系統(tǒng)
Legacy Build System
,沒有使用New Build System
。之前只是簡(jiǎn)單有過了解乐横,現(xiàn)在再深入了解一下兩個(gè)構(gòu)建系統(tǒng)的區(qū)別拥褂。
1. Xcode13編譯報(bào)錯(cuò)解決
先來解決下Xcode13編譯報(bào)錯(cuò)的問題抡驼,報(bào)錯(cuò)信息如下:
Showing All Messages
: The Legacy Build System will be removed in a future release. You can configure the selected build system and this deprecation message in File > Workspace Settings.
蘋果還是貼心的告訴我們?cè)趺慈バ薷牧耍蜷_File -> Workspace Settings
肿仑,去掉這個(gè)報(bào)錯(cuò)信息致盟,如下圖所示:
這樣就可以解決編譯報(bào)錯(cuò)的問題了碎税,但是如果工作不太忙,建議還是切換成蘋果提供的New Build System
馏锡。這樣可能會(huì)帶來一些其它未知的問題雷蹂,不過我們也都是在不斷解決問題中成長(zhǎng)的。
2. New Build System
2.1 簡(jiǎn)介
在之前Xcode9發(fā)布的時(shí)候杯道,Apple在Build System
上提供了新版本的構(gòu)建系統(tǒng)New Build System
匪煌,在WWDC2017上的介紹很簡(jiǎn)單,但是足夠覆蓋了該構(gòu)建系統(tǒng)的優(yōu)點(diǎn):降低構(gòu)建開銷党巾,尤其是可以降低大型項(xiàng)目的構(gòu)建開銷萎庭。
對(duì)于開發(fā)者,蘋果提供了足夠的過渡時(shí)間(你看我們的項(xiàng)目到現(xiàn)在才使用了New Build System
)齿拂,在Xcode9中驳规,該構(gòu)建系統(tǒng)沒有設(shè)置為默認(rèn)的構(gòu)建系統(tǒng),而在Xcode10中署海,蘋果將該系統(tǒng)設(shè)置為默認(rèn)的構(gòu)建系統(tǒng)吗购,Xcode13中,如果沒有使用New Build System
砸狞,則會(huì)報(bào)錯(cuò)了捻勉。我們可以通過Xcode->File->Project Settings/WorkSpace Settings->Build System
在新舊構(gòu)建系統(tǒng)之間進(jìn)行切換,如下圖所示:
2.2 新舊構(gòu)建系統(tǒng)的對(duì)比
2.2.1 項(xiàng)目依賴關(guān)系
實(shí)際開發(fā)中刀森,項(xiàng)目可能會(huì)依賴多個(gè)其它的工程或者三方庫踱启,這些依賴分為兩部分:
Target Dependencies
當(dāng)前Target所依賴的其它Target,被依賴的Target必須在本Target構(gòu)建之前就構(gòu)建完成
研底,除此之外沒有任何關(guān)聯(lián)禽捆。Link Binary With Libraries
指最終要Link到Product
中的文件,同時(shí)在Link到Product
中時(shí)飘哨,需要保證文件存在胚想,這就要求在構(gòu)建Target時(shí)該項(xiàng)目下的文件必須提前構(gòu)建完成
。
也就是Target無論通過哪種依賴芽隆,都需要保證被依賴的內(nèi)容在Target構(gòu)建之前久已經(jīng)被構(gòu)建成功浊服。
我們使用WWDC演示中提供的項(xiàng)目結(jié)構(gòu)(Tests Target
)用來對(duì)比兩種構(gòu)建系統(tǒng),如下圖所示:
其中連線為依賴關(guān)系胚吁,箭頭所指為被依賴target牙躺。
2.2.2 舊構(gòu)建系統(tǒng) Legacy Build System
對(duì)于程序來說,我們要構(gòu)建其中一個(gè)Target腕扶,可以確定以下幾點(diǎn):
- 所需要構(gòu)建的所有Target
- Target之間的依賴關(guān)系
- Target構(gòu)建的順序
以上圖的項(xiàng)目結(jié)構(gòu)為例孽拷,我們?nèi)绻霕?gòu)建Tests,那么圖中所有Target都需要進(jìn)行構(gòu)建半抱,對(duì)于構(gòu)建順序圖可以如下所示:
從上圖中可以看出脓恕,Target必須要等到其依賴的Target構(gòu)建完成之后才被構(gòu)建膜宋,整體是一個(gè)串行編譯的過程。而New Build System
優(yōu)化的核心思想炼幔,就是采用并行編譯
秋茫,提高編譯效率,減少編譯時(shí)間乃秀。
2.2.3 新構(gòu)建系統(tǒng) New Build System
2.2.3.1 依賴拆分
對(duì)于一開始的項(xiàng)目依賴圖肛著,我們可以先去對(duì)Tests
的依賴關(guān)系進(jìn)行摘取,如下圖所示:
可以看出Tests
的依賴可以分為三種跺讯,分別對(duì)應(yīng)Game枢贿、Shaders、Utilities
刀脏,如下圖所示:
那Tests
的構(gòu)建就不用等到Game局荚、Shaders、Utilities
三個(gè)Target都構(gòu)建完成才進(jìn)行火本。對(duì)于每一部分的構(gòu)建可以等到對(duì)應(yīng)Target構(gòu)建完成之后就可以立即進(jìn)行,如下圖所示:
由此可見聪建,通過內(nèi)容拆分钙畔,我們可以并行的進(jìn)行構(gòu)建,從而降低構(gòu)建時(shí)間金麸。
2.2.3.2 優(yōu)先構(gòu)建有用部分
再來看下Shaders
和Utilities
之間的依賴擎析,Utilities
會(huì)提供一些工具方法,而Shaders
會(huì)使用到Utilities
中的一些方法挥下,但是Shaders
并不會(huì)全部使用揍魂,只會(huì)用到Utilties
的一部分。這就為構(gòu)建優(yōu)化提供了思路:Shaders
的構(gòu)建可以在Utilities
中與之有關(guān)的內(nèi)容構(gòu)建完成之后就可以進(jìn)行棚瘟,如圖所示:
雖然Utilities
存在對(duì)Physics
的依賴现斋,但是理想狀態(tài)下,如果提取的Code Gen
不存在對(duì)Physics
的依賴偎蘸,那Code Gen
的編譯就可以提前到與Physics一個(gè)時(shí)間點(diǎn)庄蹋,如圖:
通過內(nèi)容提取,可以將某些內(nèi)容的構(gòu)建時(shí)間點(diǎn)提前迷雪,從而減少整體構(gòu)建時(shí)間限书。
2.2.3.3 遺留依賴清理
在做項(xiàng)目?jī)?yōu)化的時(shí)候,我們會(huì)刪除一些之前的無用代碼章咧,最新的代碼可能不會(huì)依賴之前的某些框架倦西,但是對(duì)于框架依賴的設(shè)置可能由于遺忘而遺留下來,我們可以通過清理這部分遺留無用依賴來加快構(gòu)建速度赁严。
在本例中扰柠,假設(shè)經(jīng)過長(zhǎng)時(shí)間的迭代粉铐,Utilities
中的內(nèi)容已經(jīng)不存在對(duì)Physics
框架的任何依賴,此時(shí)如果我們清理掉Utilities
對(duì)Physics
依賴的設(shè)置耻矮,那么Utilities
的構(gòu)建就不必等到Physics
完成了秦躯,如圖:
及時(shí)清理遺留的無用依賴設(shè)置,可以提前某些模塊的編譯時(shí)間點(diǎn)裆装,進(jìn)而減少整體構(gòu)建時(shí)間踱承。
2.2.3.4 新版Xcode的特新
- 提前編譯源碼的時(shí)間點(diǎn)
- 一旦所依賴的內(nèi)容構(gòu)建完成就可以開始構(gòu)建,無需等待全部依賴構(gòu)建
- 優(yōu)化Run Script phases的執(zhí)行來減少編譯工作
3. Run Script phases的優(yōu)化
在New Build System
中哨免,優(yōu)化了Run Script phases
的執(zhí)行工作茎活,總得來說,就是為Run Script phases
引入了依賴
的概念琢唾,進(jìn)而將Run Script phases
放入并行構(gòu)建
中载荔,從而加快構(gòu)建速度。那么Run Script phases
的依賴關(guān)系如何確定呢采桃?
3.1 Input Files/Output Files
在New Build System
中懒熙,將Input Files
和Output Files
作為該Run Script phase
的依賴關(guān)系,構(gòu)建系統(tǒng)會(huì)根據(jù)這些文件來確定Run Script phases
在構(gòu)建過程中的執(zhí)行時(shí)間點(diǎn)普办,具體原則如下:
3.2 執(zhí)行的前提
- 沒有指定
Input File
-
Input File
內(nèi)容改變 -
Output File
丟失
3.3 執(zhí)行時(shí)間點(diǎn)
- 若沒有指定
Input File
工扎,執(zhí)行時(shí)間點(diǎn)會(huì)在構(gòu)建最開始
- 若指定了
Input File
,則需要保證Input File
構(gòu)建完成
3.2 Input Files List/Output Files List
蘋果為了避免開發(fā)者在執(zhí)行腳本時(shí)可能指定過多的Input Files
或Output Files
衔蹲,新增了Input Files List
和Output Files List
肢娘,在這兩個(gè)參數(shù)中,可以指定一個(gè)后綴為.xcfilelist
的文件舆驶,在該文件中列舉所需依賴的Input Files
和Output Files
橱健,文件內(nèi)容格式如下:
3.3 New Build System可能引起的問題
開發(fā)者可能在項(xiàng)目中設(shè)置一些Script,在其中可能會(huì)做一些Build version沙廉、App Icon
等的設(shè)置拘荡,這些腳本在舊的串行構(gòu)建系統(tǒng)中會(huì)在最后執(zhí)行
,最終完成所需內(nèi)容的替換撬陵,達(dá)到所需目的俱病。
但是在新構(gòu)建系統(tǒng)中,若不做特殊設(shè)置袱结,該腳本會(huì)在并行構(gòu)建的開始階段就執(zhí)行亮隙,從而無法保證最終的替換能夠生效(可能會(huì)被其余構(gòu)建過程替換)。
解決方式就是為腳本設(shè)置好依賴關(guān)系垢夹,從而保證腳本執(zhí)行在Target構(gòu)建
之后溢吻。我們知道,Process Info.plist
過程會(huì)為.app
文件生成Info.plst
文件并進(jìn)行初始化,我們可以將該文件設(shè)置為Run Script phases
的Input Files
促王,保證腳本的執(zhí)行時(shí)間點(diǎn)在Info.plist
更改之后犀盟,進(jìn)而保證腳本的執(zhí)行結(jié)果有效。
4. 遇到的問題
在切換New Build System
時(shí)確實(shí)遇到了一個(gè)問題蝇狼,報(bào)錯(cuò)信息如下:
產(chǎn)生這個(gè)問題的原因是多個(gè)命令生成了.car
文件阅畴。為了研究這個(gè)問題,還需要了解下Pod庫圖片資源的引用方式迅耘。
5. Pod庫圖片資源的引用方式
包括兩種:resource_bundles
和 resources
贱枣。
5.1 resource_bundles
resource_bundles
允許定義當(dāng)前 Pod 庫的資源包的名稱和文件。用 hash 的形式來聲明颤专,key 是 bundle 的名稱纽哥,value 是需要包括的文件的通配 patterns。
官方推薦使用
resource_bundles
方式引用圖片資源栖秕,同時(shí)建議 bundle 的名稱至少應(yīng)該包括 Pod 庫的名稱春塌,可以盡量減少同名沖突。
使用方式如下:
# LibResources 是可以自定義的Bundle的名字
# Resources 是創(chuàng)建的Pod庫的名稱
s.resource_bundles = {
'LibResources' => ['Resources/Assets/**/*.png']
}
在pod install
之后簇捍,構(gòu)建一下只壳,打開Products
下的.app
文件,顯示包內(nèi)容暑塑。
5.1.1 靜態(tài)庫.a形式
- 如果使用的是
.a
吼句,靜態(tài)pod庫的依賴方式,可以看到.app
下會(huì)有一個(gè)LibResources.bundle
文件梯投,存放Pod庫用的圖片資源文件命辖,如下圖:
5.1.2 動(dòng)態(tài)庫.framework形式
- 如果使用的是
.framework
動(dòng)態(tài)庫的依賴方式况毅,可以看到在.app
內(nèi)會(huì)有一個(gè)和Pod同名的Resources.framework
分蓖,里面有一個(gè)LibResources.bundle
文件,如圖:
因?yàn)镻od可能作為動(dòng)態(tài)庫
或者靜態(tài)庫
的形式提供給工程使用尔许,為了兼容這兩種情況么鹤,使用bundleForClass:
來獲取Pod的bundle,當(dāng)Pod作為靜態(tài)庫時(shí)味廊,該方法返回的是mainBundle
蒸甜,當(dāng)Pod作為動(dòng)態(tài)庫時(shí),該方法返回的就是動(dòng)態(tài)庫本身
余佛。
所以在使用resource_bundles
這種方式引用pod庫中的圖片資源時(shí)柠新,在pod庫中使用圖片的代碼如下:
+ (nullable UIImage *)imageName:(NSString *)name {
static NSBundle *resourceBundle = nil;
if (resourceBundle == nil) {
resourceBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[xxxx(pod庫任意類名) class]] pathForResource:@"xxx(bundle名稱)" ofType:@"bundle"]];
}
UIImage *image = [UIImage imageNamed:name inBundle:resourceBundle compatibleWithTraitCollection:nil];
return image;
}
5.2 resources
使用 resources
來指定資源,被指定的資源只會(huì)簡(jiǎn)單的被 copy 到目標(biāo)工程中(主工程)辉巡。
官方認(rèn)為用 resources 是無法避免同名資源文件的沖突的恨憎,同時(shí),Xcode 也不會(huì)對(duì)這些資源做優(yōu)化。
使用示例:
spec.resources = 'Images/*'
5.2.1 以靜態(tài)庫.a的形式
如果pod庫是以靜態(tài)庫.a文件的形式提供的憔恳,這樣只是會(huì)拷貝到.app
內(nèi)瓤荔,如下圖所示:
這種圖片資源引用方式跟我們直接把圖片放到主工程項(xiàng)目下的存放方式是一樣的,都是直接copy到.app
下钥组,所以在pod庫中输硝,可以使用imageNamed:
方法獲取圖片,在主工程中也可以通過imageNamed:
方法獲取pod庫中的圖片程梦,如下:
UIImage *image = [UIImage imageNamed:@"share_bgImage"];
這可能會(huì)導(dǎo)致同名資源文件的沖突点把,如果主工程中也有一個(gè)圖片名字為share_bgImage
,編譯時(shí)就會(huì)報(bào)錯(cuò)作烟,如下圖所示:
5.2.2 以動(dòng)態(tài)庫.framework的形式
最后也會(huì)存在.app
下和pod庫同名的.framework
文件夾下愉粤,如下圖所示:
在pod庫中使用這個(gè)圖片時(shí),需要先獲取到圖片所在的bundle拿撩,再根據(jù)圖片名字獲取圖片衣厘,如下所示:
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
if (bundle) {
return [UIImage imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil];
}
所以在構(gòu)建pod庫時(shí)還是使用resource_bundles
這種圖片引用方式。
綜上可知压恒,不同的圖片資源引用方式和不同的pod庫使用形式導(dǎo)致最后圖片資源的位置是不一樣的影暴,如下所示:
- | 動(dòng)態(tài)庫.framework | 靜態(tài)庫.a |
---|---|---|
resources | xxx.app/xxx.framework | xxx.app |
resource_bundles | xxx.app/xxx.framework/xxx.bundle | xxx.app/xxx.bundle |
5.3 在pod庫中使用.xcassets
管理圖片
pod庫中使用.xcassets
管理不同分辨率的圖片會(huì)更加方便,使用方式如下:
s.resources = ["Resources/XCA/*.xcassets"]
但是pod在使用.xcassets
探赫,編譯的時(shí)候會(huì)生成一個(gè)Assets.car
文件型宙,可以在Build Phase -> [CP] Copy Pods Resources -> Output File Lists
下看到:
${PODS_ROOT}/Target Support Files/Pods-HTDemo/Pods-HTDemo-resources-${CONFIGURATION}-output-files.xcfilelist
打開這個(gè)文件,在最下方能看到會(huì)生成一個(gè)Assets.car
文件伦吠,如下圖所示:
而我們的主項(xiàng)目也會(huì)生成一個(gè)Assets.car
文件妆兑,那么就可能會(huì)
產(chǎn)生沖突,編譯報(bào)錯(cuò)毛仪,也就是上面第4點(diǎn)
中遇到的編譯錯(cuò)誤搁嗓。
為什么說可能會(huì)產(chǎn)生錯(cuò)誤
?因?yàn)樯厦嫖覀冎?code>resources和resource_bundles
和pod庫的使用方式會(huì)導(dǎo)致圖片資源存放的位置發(fā)生變化箱靴,如果我們使用了resources
管理.xcassets
腺逛,并且pod庫是以靜態(tài)庫.a
的方式提供的,那就會(huì)導(dǎo)致編譯報(bào)錯(cuò)衡怀。
pod庫生成的Assets.car
文件會(huì)存放到.app
下棍矛,主工程生成的Assets.car
也會(huì)存放到.app
下,產(chǎn)生沖突抛杨,就報(bào)錯(cuò)了够委。
解決這個(gè)問題,有兩種方案怖现,第一種就是使用resource_bundles
茁帽,
s. resource_bundles = { "bundleName" => ["Resources/XCA/*.xcassets"]}
第二種就是屏蔽[CP] Copy Pods Resources
下的輸入和輸出路徑,在podfile
中加入:
install! 'cocoapods',
:disable_input_output_paths => false
重新pod install
即可,可以看到[CP] Copy Pods Resources
下的輸入和輸出路徑都沒有了:
那此時(shí)圖片去哪了呢脐雪?圖片會(huì)合并到主項(xiàng)目生成的Assets.car
中厌小,可以把主項(xiàng)目中的Assets.xcassets
中的圖片刪除,pod庫中的Assets.xcassets
中的圖片保留試一試战秋,最后生成的.app
下還是會(huì)有一個(gè)Assets.car
文件璧亚。