啟動(dòng)時(shí)間優(yōu)化

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)化的地方

15.png
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中。

1.png

優(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)化兜蠕,可以不管。

image-20201026163408822.png
1.2.2抛寝、靜態(tài)庫(kù)

在Pod的工程中熊杨,選擇我們使用的庫(kù),然后點(diǎn)擊Build Setting盗舰,搜索或者找到Mach-O Type設(shè)置晶府,修改Mach-O Type為static Library

image-20201026163814646.png

按照上面的步驟,把我們的動(dòng)態(tài)庫(kù)都轉(zhuǎn)化成靜態(tài)庫(kù)钻趋,先執(zhí)行一次Clean Build Folder 川陆,然后重新構(gòu)建一次

image-20201026164051274.png

這里保留了連個(gè)OC的庫(kù)

1.2.3、遇到的坑
2.png

其實(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到底改變了什么

3.png

Podfile文件中配置了use_frameworks!,然后進(jìn)行pod install磨德,這樣生成的就是動(dòng)態(tài)庫(kù)缘回。

首先,看一下這個(gè)庫(kù)的Mach-O Type是動(dòng)態(tài)庫(kù)

4.png

執(zhí)行?+B構(gòu)建之后典挑,我們還是來(lái)到Products文件中的app:

5.png

在生成的Demo.app文件包上面點(diǎn)右鍵酥宴,選擇顯示包內(nèi)容:

6.png

打開(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)的代碼的浑玛。

7.png

下面我們修改Build Settings中的Mach-O Type绍申,將其設(shè)置為靜態(tài)庫(kù)Static Library。

8.png

和上面一樣我們這邊直接替換Pods-Demo-frameworks.sh中install_framework:

image-20201026170729212.png

我們看到我們?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争舞。

9.png

先執(zhí)行Clean Build Folder(或?+?+K)凛忿,然后再?+B進(jìn)行構(gòu)建,這時(shí)就會(huì)出現(xiàn)duplicate symbols報(bào)錯(cuò):

10.png

解決辦法:
任意一個(gè)或者都不使用靜態(tài)庫(kù)竞川。雖然這么說(shuō)店溢,其實(shí)這也是不安全的。如果能改名字就改一下吧流译。

11.png
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ù)的情況:

12.png

我們看到Main Bundle是我們的App,而我們的Pod1 Bundle和Pod2 Bundle分別是其對(duì)應(yīng)的framework福澡,類似于它們有自己的沙盒。

我們?cè)賮?lái)看看靜態(tài)庫(kù):

13.png

可以看到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ù)的選擇

14.png

參考資料

[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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市胖秒,隨后出現(xiàn)的幾起案子缎患,更是在濱河造成了極大的恐慌,老刑警劉巖阎肝,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挤渔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡风题,警方通過(guò)查閱死者的電腦和手機(jī)判导,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沛硅,“玉大人眼刃,你說(shuō)我怎么就攤上這事∫〖。” “怎么了擂红?”我有些...
    開(kāi)封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)朦蕴。 經(jīng)常有香客問(wèn)我篮条,道長(zhǎng)弟头,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任涉茧,我火速辦了婚禮赴恨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伴栓。我一直安慰自己伦连,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布钳垮。 她就那樣靜靜地躺著惑淳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饺窿。 梳的紋絲不亂的頭發(fā)上歧焦,一...
    開(kāi)封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音肚医,去河邊找鬼绢馍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肠套,可吹牛的內(nèi)容都是我干的舰涌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼你稚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瓷耙!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起刁赖,我...
    開(kāi)封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搁痛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后乾闰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體落追,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年涯肩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轿钠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡病苗,死狀恐怖疗垛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硫朦,我是刑警寧澤贷腕,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響泽裳,放射性物質(zhì)發(fā)生泄漏瞒斩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一涮总、第九天 我趴在偏房一處隱蔽的房頂上張望胸囱。 院中可真熱鬧,春花似錦瀑梗、人聲如沸烹笔。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谤职。三九已至,卻和暖如春亿鲜,著一層夾襖步出監(jiān)牢的瞬間允蜈,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工狡门, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陷寝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓其馏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親爆安。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叛复,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354