1狠怨、APP啟動(dòng)
1.1、APP啟動(dòng)為什么這么重要
- App 啟動(dòng)是和用戶的第一個(gè)交互過(guò)程饵逐,所以要盡量縮短這個(gè)過(guò)程的時(shí)間盯质,給用戶一個(gè)良好的第一印象
- 啟動(dòng)代表了你的代碼的整體性能袁串,如果啟動(dòng)的性能不好,其他部分的性能可能也不會(huì)太好
- 啟動(dòng)會(huì)占用 CPU 和內(nèi)存呼巷,從而影響系統(tǒng)性能和電池
1.2囱修、啟動(dòng)類型
Cold Launch 也就是冷啟動(dòng),冷啟動(dòng)需要滿足以下幾個(gè)條件:
- 重啟之后
- App 不在內(nèi)存中
- 沒(méi)有相關(guān)的進(jìn)程存在
Warm Launch 也就是熱啟動(dòng)王悍,熱啟動(dòng)需要滿足以下幾個(gè)條件:
- App 剛被終止
- App 還沒(méi)完全從內(nèi)存中移除
- 沒(méi)有相關(guān)的進(jìn)程存在
Resume Launch 指的是被掛起的 App 繼續(xù)的過(guò)程破镰,需要滿足以下幾個(gè)條件:
- App 被掛起
- App 還全部都在內(nèi)存中
- 還存在相關(guān)的進(jìn)程
1.4、App 啟動(dòng)階段
App 啟動(dòng)分為三個(gè)階段
- 初始化 App 的準(zhǔn)備工作
- 繪制第一幀 App 的準(zhǔn)備工作及繪制(這里的第一幀并不是獲取到數(shù)據(jù)之后的第一幀压储,可以是一張占位視圖)鲜漩,這時(shí)候用戶與App已經(jīng)可以交互了,比如 tabbar 切換
- 獲取到頁(yè)面的所有數(shù)據(jù)之后的完整的繪制第一幀頁(yè)面
在這個(gè)地方渠脉,蘋果再次強(qiáng)調(diào)了一下宇整,建議「用戶從點(diǎn)擊 App 圖標(biāo)到可以再次交互,也就是第二階段結(jié)束」的時(shí)間最好在 400ms 以內(nèi)芋膘。目前來(lái)看,大部分 App 都沒(méi)有達(dá)到這個(gè)目標(biāo)。
下面我們把上面的三個(gè)階段分成下面6個(gè)部分为朋,講一下這幾個(gè)階段做了什么以及有什么可以優(yōu)化的地方
1.4.1臂拓、System Interface
初始化APP的準(zhǔn)備工作,系統(tǒng)主要做了兩件事:Load dylibs 和 libSystem init
在Load dylibs 階段习寸,開(kāi)發(fā)者還可以做一下優(yōu)化:
- 避免連接無(wú)用的framworks胶惰,在Xcode中檢查一下項(xiàng)目中「Linked Frameworks and Librares」部分是否有無(wú)用的連接
- 避免在啟動(dòng)時(shí)加載動(dòng)態(tài)庫(kù),將項(xiàng)目的Pods以靜態(tài)編譯的方式打包霞溪,尤其是Swift項(xiàng)目孵滞,這地方時(shí)間損耗是很大的
- 硬鏈接你的依賴庫(kù),這里做了緩存優(yōu)化
LibSystem init 部分鸯匹,主要是加載一些優(yōu)先級(jí)比較低的系統(tǒng)組件坊饶,這部分時(shí)間是一個(gè)固定的成本,所以我們開(kāi)發(fā)人員不需要關(guān)心
Static Runtime Initalization
這個(gè)階段主要是OC和Swift Runtime的初始化時(shí)間殴蓬,會(huì)調(diào)用所有的 +load 方法匿级,將類的信息注冊(cè)到Runtime中
在這個(gè)階段原則上不建議卡發(fā)著做任何事情,所以為了避免一些啟動(dòng)時(shí)間的損耗染厅,你可以做一下幾個(gè)事情:
- 在Frameworks 開(kāi)發(fā)時(shí)痘绎,公用專用的初始化API
- 減少在 +load 中做事情
- 使用 initialize進(jìn)行來(lái)加載初始化工作
1.4.2、UIKit Initalization
這個(gè)階段主要做了兩件事情:
- 實(shí)例化 UIApplication和UIApplicationDelegate
- 開(kāi)始事件處理和系統(tǒng)集成
所以這個(gè)階段的優(yōu)化也比較簡(jiǎn)單肖粮,你需要做兩件事:
- 最大限度的減少UIApplication子類初始化時(shí)候的工作
- 減少UIApplicationDelegate的初始化工作
1.4.3孤页、Application Initialization
這個(gè)階段主要是生命周期方法回調(diào),也正是開(kāi)發(fā)者熟悉的部分
調(diào)用UIApplicationDelegate的APP生命周期方法:
application:willFinishLaunchingWithOptions:
application:didFinishLaunchingWithOptions:
和 UIApplicationDelegate 的 UI 生命周期方法:
applicationDidBecomeActive:
同時(shí)涩馆,iOS 13 針對(duì) UISceneDelegate 增加了新的回調(diào):
scene:willConnectToSession:options:
sceneWillEnterForeground:
sceneDidBecomeActive:
也會(huì)在這個(gè)階段調(diào)用行施。感興趣的可以關(guān)注一下 Getting the Most out of Multitasking 這個(gè) Session,暫時(shí)沒(méi)有視頻資源凌净,懷疑是現(xiàn)場(chǎng)演示翻車了悲龟,所以沒(méi)有把視頻資源放出來(lái)。
在這個(gè)階段冰寻,開(kāi)發(fā)者可以做的優(yōu)化:
- 推遲和啟動(dòng)時(shí)無(wú)關(guān)的工作
- Senens 之間共享資源
1.4.4须教、Fisrt Frame Render
這個(gè)階段主要做了創(chuàng)建、布局和繪制視圖的工作斩芭,并把準(zhǔn)備好的第一幀提交給渲染層渲染轻腺。會(huì)頻繁調(diào)用以下幾個(gè)函數(shù):
loadView
viewDidLoad
layoutSubviews
在這個(gè)階段,開(kāi)發(fā)者可以做的優(yōu)化:
- 減少視圖層級(jí)划乖,懶加載一些不需要的視圖
- 優(yōu)化布局贬养,減少約束
1.4.5、Extend
大部分 App 都會(huì)通過(guò)異步的方式獲取數(shù)據(jù)琴庵,并最終呈現(xiàn)給用戶误算。我們把這一部分稱為 Extend仰美。
2、動(dòng)態(tài)庫(kù)轉(zhuǎn)靜態(tài)庫(kù)
蘋果建議將應(yīng)用程序的總啟動(dòng)時(shí)間設(shè)定在400毫秒以下儿礼,并且我們必須在20秒之內(nèi)完成啟動(dòng)咖杂,否則系統(tǒng)會(huì)殺死我們的應(yīng)用程序。我們可以盡量?jī)?yōu)化應(yīng)用main函數(shù)到didFinishLaunchingWithOptions的時(shí)間蚊夫,但如何調(diào)試在調(diào)用代碼之前發(fā)生的啟動(dòng)速度慢的情況呢诉字?
1.1、Pre-main時(shí)間的查看
在系統(tǒng)執(zhí)行應(yīng)用程序的main函數(shù)并調(diào)用應(yīng)用程序委托函數(shù)(applicationWillFinishLaunching)之前知纷,會(huì)發(fā)生很多事情壤圃。我們可以將DYLD_PRINT_STATISTICS環(huán)境變量添加到項(xiàng)目scheme中。
優(yōu)化前
Total pre-main time: 2.0 seconds (100.0%)
dylib loading time: 1.7 seconds (84.3%)
rebase/binding time: 35.25 milliseconds (1.7%)
ObjC setup time: 40.95 milliseconds (1.9%)
initializer time: 244.57 milliseconds (11.9%)
slowest intializers :
libSystem.B.dylib : 12.97 milliseconds (0.6%)
Alamofire : 106.12 milliseconds (5.1%)
優(yōu)化后
Total pre-main time: 1.2 seconds (100.0%)
dylib loading time: 1.1 seconds (89.1%)
rebase/binding time: 23.51 milliseconds (1.8%)
ObjC setup time: 25.41 milliseconds (1.9%)
initializer time: 91.64 milliseconds (7.0%)
slowest intializers :
libSystem.B.dylib : 7.15 milliseconds (0.5%)
FellorliSwift : 35.71 milliseconds (2.7%)
這是我使用iPhone 5c的運(yùn)行結(jié)果 琅轧,這只是通過(guò)staticlib優(yōu)化從啟動(dòng)2秒時(shí)間降低到1.2秒伍绳。這里講一下各部分的作用
注意:如果你要測(cè)試應(yīng)用的最慢啟動(dòng)時(shí)間,記得使用你支持的最慢的設(shè)備來(lái)進(jìn)行測(cè)試鹰晨。
輸出顯示系統(tǒng)調(diào)用應(yīng)用程序main時(shí)所用的總時(shí)間墨叛,然后是主要步驟的分解。
WWDC 2016 Session 406優(yōu)化應(yīng)用程序啟動(dòng)時(shí)間詳細(xì)介紹了每個(gè)步驟以及改進(jìn)時(shí)間的提示模蜡,以下是簡(jiǎn)要的總結(jié)說(shuō)明:
? dylib loading time 動(dòng)態(tài)加載程序查找并讀取應(yīng)用程序使用的依賴動(dòng)態(tài)庫(kù)漠趁。每個(gè)庫(kù)本身都可能有依賴項(xiàng)。雖然蘋果系統(tǒng)框架的加載是高度優(yōu)化的忍疾,但加載嵌入式框架可能會(huì)很耗時(shí)闯传。為了加快動(dòng)態(tài)庫(kù)的加載速度,蘋果建議您使用更少的動(dòng)態(tài)庫(kù)卤妒,或者考慮合并它們甥绿。
* 建議的目標(biāo)是六個(gè)額外的(非系統(tǒng))框架。
? Rebase/binding time 修正調(diào)整鏡像內(nèi)的指針(重新調(diào)整)和設(shè)置指向圖像外符號(hào)的指針(綁定)则披。為了加快重新定位/綁定時(shí)間共缕,我們需要更少的指針修復(fù)。
* 如果有大量(大的是20000)Objective-C類士复、選擇器和類別的應(yīng)用程序可以增加800ms的啟動(dòng)時(shí)間图谷。
* 如果應(yīng)用程序使用C++代碼,那么使用更少的虛擬函數(shù)阱洪。
* 使用Swift結(jié)構(gòu)體通常也更快便贵。
? ObjC setup time Objective-C運(yùn)行時(shí)需要進(jìn)行設(shè)置類、類別和選擇器注冊(cè)冗荸。我們對(duì)重新定位綁定時(shí)間所做的任何改進(jìn)也將優(yōu)化這個(gè)設(shè)置時(shí)間承璃。
? initializer time 運(yùn)行初始化程序。如果使用了Objective-C的 +load
方法蚌本,請(qǐng)將其替換為 +initialize
方法盔粹。
在系統(tǒng)調(diào)用main之后隘梨,main將依次調(diào)用UIApplicationMain和應(yīng)用程序委托方法。
1.2玻佩、動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù)
1.2.1出嘹、動(dòng)態(tài)庫(kù)
我們先來(lái)看看工程里面有多少動(dòng)態(tài)庫(kù):
? 在項(xiàng)目的Product文件夾找到我們的工程.app文件席楚,右鍵選擇Show in Finder咬崔。
? 來(lái)到相應(yīng)目錄后右鍵選擇顯示包內(nèi)容。
? 找到Frameworks文件夾烦秩,打開(kāi)垮斯。
? 項(xiàng)目是純Swift編寫,下面都是系統(tǒng)Swift庫(kù)只祠,我們沒(méi)法優(yōu)化兜蠕,可以不管。
1.2.2抛寝、靜態(tài)庫(kù)
在Pod的工程中熊杨,選擇我們使用的庫(kù),然后點(diǎn)擊Build Setting盗舰,搜索或者找到Mach-O Type設(shè)置晶府,修改Mach-O Type為static Library
按照上面的步驟,把我們的動(dòng)態(tài)庫(kù)都轉(zhuǎn)化成靜態(tài)庫(kù)钻趋,先執(zhí)行一次Clean Build Folder 川陆,然后重新構(gòu)建一次
這里保留了連個(gè)OC的庫(kù)
1.2.3、遇到的坑
其實(shí)這里是CocoaPods的一個(gè)配置問(wèn)題蛮位,CocoaPods會(huì)在項(xiàng)目中的Build Phases添加一個(gè)[CP] Embed Pods Frameworks執(zhí)行腳本
"${PODS_ROOT}/Target Support Files/Pods-項(xiàng)目名/Pods-項(xiàng)目名-frameworks.sh"
我們?cè)趫?zhí)行pod install后會(huì)生成一個(gè)Pods-項(xiàng)目名-frameworks.sh的腳本文件较沪。由于我們是手動(dòng)修改的Mach-O Type類型,這個(gè)腳本中的install_framework仍然會(huì)執(zhí)行失仁,所以我們要把轉(zhuǎn)換成靜態(tài)庫(kù)的這些庫(kù)從Pods-項(xiàng)目名-frameworks.sh文件中刪除尸曼。
我們先看一下install_framework 到底干了啥
# Copies and strips a vendored framework
install_framework()
{
# 設(shè)置source變量,三方庫(kù)構(gòu)建之后的路徑
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
local source="${BUILT_PRODUCTS_DIR}/$1"
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
elif [ -r "$1" ]; then
local source="$1"
fi
# 設(shè)置destination變量萄焦,三方庫(kù)需要移動(dòng)到的路徑
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
# 判斷source是否為鏈接文件控轿,需要指向原來(lái)的文件
if [ -L "${source}" ]; then
echo "Symlinked..."
source="$(readlink "${source}")"
fi
# rsync --delete無(wú)差異同步,可以簡(jiǎn)單理解為網(wǎng)盤同步楷扬,或者復(fù)制
# 想詳細(xì)了解rsync解幽,可以在命令行中輸入man rsync
# 這里相當(dāng)于把source的文件(文件夾)同步到destination
# 即把*.framework復(fù)制到Frameworks文件夾下
# Use filter instead of exclude so missing patterns don't throw errors.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
# 下面是找到二進(jìn)制文件,即framework的Mach-O
local basename
basename="$(basename -s .framework "$1")"
binary="${destination}/${basename}.framework/${basename}"
if ! [ -r "$binary" ]; then
binary="${destination}/${basename}"
elif [ -L "${binary}" ]; then
echo "Destination binary is symlinked..."
dirname="$(dirname "${binary}")"
binary="${dirname}/$(readlink "${binary}")"
fi
# 去掉無(wú)效的架構(gòu)
# Strip invalid architectures so "fat" simulator / device frameworks work on device
if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
strip_invalid_archs "$binary"
fi
# 進(jìn)行代碼簽名
# Resign the code if required by the build settings to avoid unstable apps
code_sign_if_enabled "${destination}/$(basename "$1")"
# Swift的運(yùn)行時(shí)庫(kù)烘苹,Xcode 7之后就用不到了躲株,可以不管
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
local swift_runtime_libs
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
for lib in $swift_runtime_libs; do
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
code_sign_if_enabled "${destination}/${lib}"
done
fi
}
install_framework是把構(gòu)建好的 *.framework
包復(fù)制到App的Frameworks文件夾下
出現(xiàn)上面的報(bào)錯(cuò)就是因?yàn)橘Y源沒(méi)有從 *.framework中轉(zhuǎn)移到App中。
解決辦法:
既然現(xiàn)在拿到的Bundle是Main Bundle镣衡,我們構(gòu)建之后利用腳本把資源拷貝到APP文件夾不就好了
install_framework_bundle()
{
# 設(shè)置source變量霜定,三方庫(kù)構(gòu)建之后的路徑
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
local source="${BUILT_PRODUCTS_DIR}/$1"
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
elif [ -r "$1" ]; then
local source="$1"
fi
# 設(shè)置destination變量档悠,三方庫(kù)需要移動(dòng)到的路徑
local destination="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
# 遍歷framework下的文件,找到bundle和圖片望浩,有其他資源自己改一下
for filename in `ls ${source} | grep ".*\.bundle\|.*\.jpg\|.*\.jpeg\|.*\.png"`
do
full_path=${source}/${filename}
# 把資源同步到Main Bundle中
rsync -abrv --suffix .conflict "${full_path}" "${destination}"
done
}
現(xiàn)在我們的操作就是把被靜態(tài)化的三方庫(kù)從install_framework方法改為install_framework_bundle:
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/HandyJSON/HandyJSON.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Hero/Hero.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MQTTClient/MQTTClient.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/PKHUD/PKHUD.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxAlamofire/RxAlamofire.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/SwiftyUserDefaults/SwiftyUserDefaults.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Toast-Swift/Toast_Swift.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/HandyJSON/HandyJSON.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Hero/Hero.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MQTTClient/MQTTClient.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/PKHUD/PKHUD.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxAlamofire/RxAlamofire.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/SwiftyUserDefaults/SwiftyUserDefaults.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Toast-Swift/Toast_Swift.framework"
fi
3辖所、修改Mach-O Type到底改變了什么
Podfile文件中配置了use_frameworks!,然后進(jìn)行pod install磨德,這樣生成的就是動(dòng)態(tài)庫(kù)缘回。
首先,看一下這個(gè)庫(kù)的Mach-O Type是動(dòng)態(tài)庫(kù)
執(zhí)行?+B構(gòu)建之后典挑,我們還是來(lái)到Products文件中的app:
在生成的Demo.app文件包上面點(diǎn)右鍵酥宴,選擇顯示包內(nèi)容:
打開(kāi)Framewoks文件夾,我們可以看到里面有我們創(chuàng)建的兩個(gè)動(dòng)態(tài)Pod1.framework和Pod2.framework您觉。文件夾里面有代碼簽名拙寡、資源、Info.plist琳水、Pod1(Mach-O)肆糕、bundle。
也就是說(shuō)在孝,如果我們使用的是動(dòng)態(tài)庫(kù)诚啃,在Framewoks文件夾就會(huì)看到它的身影,同時(shí)主工程的Mach-O文件中是沒(méi)有相關(guān)的代碼的浑玛。
下面我們修改Build Settings中的Mach-O Type绍申,將其設(shè)置為靜態(tài)庫(kù)Static Library。
和上面一樣我們這邊直接替換Pods-Demo-frameworks.sh中install_framework:
我們看到我們?cè)趦蓚€(gè)庫(kù)中創(chuàng)建的類Pod1Object和Pod2Object來(lái)到了主工程的Mach-O文件中顾彰!
現(xiàn)在應(yīng)該明白了:
? 動(dòng)態(tài)庫(kù)會(huì)和主工程的Mach-O分開(kāi)存放极阅。
? 靜態(tài)庫(kù)會(huì)和主工程的Mach-O合并在一起
4、靜態(tài)庫(kù)帶來(lái)的問(wèn)題
我們看到我們?cè)趦蓚€(gè)庫(kù)中創(chuàng)建的類Pod1Object和Pod2Object來(lái)到了主工程的Mach-O文件中涨享!
現(xiàn)在應(yīng)該明白了:
? 動(dòng)態(tài)庫(kù)會(huì)和主工程的Mach-O分開(kāi)存放筋搏。
? 靜態(tài)庫(kù)會(huì)和主工程的Mach-O合并在一起
4.1、符號(hào)沖突
回顧下 -ObjC 厕隧、 -all_load 奔脐、-force_load這三個(gè)flag的區(qū)別:
? -ObjC 鏈接器會(huì)加載靜態(tài)庫(kù)中所有的Objective-C類和Category;(導(dǎo)致可執(zhí)行文件變大)
? -all_load 鏈接器會(huì)加載靜態(tài)庫(kù)中所有的Objective-C類和Category(這里和上面一樣)吁讨;當(dāng)靜態(tài)庫(kù)只有Category時(shí) -ObjC會(huì)失效髓迎,需要使用這個(gè)flag;
? -force_load 加載特定靜態(tài)庫(kù)的全部類建丧,與 -all_load類似但是只限定于特定靜態(tài)庫(kù)排龄,所以 -force_load需要指定靜態(tài)庫(kù);當(dāng)兩個(gè)靜態(tài)庫(kù)存在同樣的符號(hào)時(shí)翎朱,使用 -all_load會(huì)出現(xiàn) duplicate symbol的錯(cuò)誤橄维,此時(shí)可以根據(jù)情況選擇將其中一個(gè)庫(kù) -force_load尺铣。
我們?cè)赑od1庫(kù)中復(fù)制一份Pod2Object.{h,m},同時(shí)在Build Settings中的Other Linker Flags中添加 -all_load争舞。
先執(zhí)行Clean Build Folder(或?+?+K)凛忿,然后再?+B進(jìn)行構(gòu)建,這時(shí)就會(huì)出現(xiàn)duplicate symbols報(bào)錯(cuò):
解決辦法:
任意一個(gè)或者都不使用靜態(tài)庫(kù)竞川。雖然這么說(shuō)店溢,其實(shí)這也是不安全的。如果能改名字就改一下吧流译。
4.2逞怨、Bundle的獲取
我們?cè)赑od1Object和Pod2Object中添加以下方法:
- (nullable NSBundle *)getBundle {
return [NSBundle bundleForClass:[self class]];
}
再在主工程的ViewController中添加:
- (void)viewDidLoad {
[super viewDidLoad];
NSBundle *main = [NSBundle mainBundle];
NSBundle *pod1 = [[Pod1Object new] getBundle];
NSBundle *pod2 = [[Pod2Object new] getBundle];
NSLog(@"%@", main);
NSLog(@"%@", pod1);
NSLog(@"%@", pod2);
}
我們先看一下動(dòng)態(tài)庫(kù)的情況:
我們看到Main Bundle是我們的App,而我們的Pod1 Bundle和Pod2 Bundle分別是其對(duì)應(yīng)的framework福澡,類似于它們有自己的沙盒。
我們?cè)賮?lái)看看靜態(tài)庫(kù):
可以看到3個(gè)Bundle都變成了我們的Main Bundle驹马!
這是因?yàn)殪o態(tài)庫(kù)被合并到了主工程Mach-O文件中:
[NSBundle bundleForClass:[self class]];
[self class]
現(xiàn)在在主工程的Mach-O中革砸,那么上面找到的自然是主工程的Bundle,即Main Bundle糯累。
這個(gè)問(wèn)題解決起來(lái)比符號(hào)沖突簡(jiǎn)單一些算利,但解決這個(gè)問(wèn)題前,我要先講一下CocoaPods泳姐。
5效拭、動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的選擇
參考資料
[1] WWDC 2019 keynote: https://developer.apple.com/videos/play/wwdc2019/101/
[2] WWDC2019 - 423 - Optimizing App Launch: https://developer.apple.com/videos/play/wwdc2019/423/
[3] dyld啟動(dòng)流程: https://leylfl.github.io/2018/05/28/dyld啟動(dòng)流程/
[4]WWDC2017 - 413 - App Startup Time: Past, Present, and Future: https://developer.apple.com/videos/play/wwdc2017/413/
[5] Static linking vs dyld3: https://allegro.tech/2018/05/Static-linking-vs-dyld3.html
[6] WWDC2018 - 220 - High Performance Auto Layout: https://developer.apple.com/videos/play/wwdc2018/220/
[7] WWDC2019 - 417 - Improving Battery Life and Performance: https://developer.apple.com/videos/play/wwdc2019/417/
[8] WWDC2017 - 706 - Modernizing Grand Central Dispatch Usage: https://developer.apple.com/videos/play/wwdc2017/706/
[9] The Talk Show Live From WWDC 2019, With Craig Federighi and Greg Joswiak: https://daringfireball.net/2019/06/the_talk_show_live_from_wwdc_2019
[10] MetricKit: https://developer.apple.com/documentation/metrickit
[11 ]WWDC2019 - 417 -Improving Battery Life and Performance: https://developer.apple.com/videos/play/wwdc2019/417/