「iOS」————APP啟動優化

iOS學習

  • APP的啟動流程
    • 啟動
    • 流程
      • 缺頁錯誤
    • 主要階段
      • pre-main階段
      • main階段
    • 啟動優化
      • pre-main
      • main階段
      • 啟動優化總結
      • 流程總結


APP的啟動流程

啟動

首先我們來了解啟動的概念:

  • 廣義上的啟動是點擊圖標到首頁數據加載完畢
  • 狹義上的啟動是點擊圖標到啟動圖完全消失的第一幀

啟動的最佳時間是400ms以內,因為從點擊圖標到顯示Launch Screen,到Launch Screen消失這段時間是400ms。啟動時間不可以大于20s,否則會被系統殺掉

啟動的計算方式如下:

  • 起點:進程創建的時間
  • 終點:第一個CA::Transaction::commit()

啟動分為冷啟動和熱啟動:

冷啟動:系統里沒有任何進程的緩存信息,典型的是重啟手機后直接啟動 App

熱啟動:從后臺再次進入App時,這次啟動就是熱啟動,因為進程緩存還在

那么我們使用app時,哪個啟動更多呢?

這實際上要看產品的形態,打開的頻次越高,熱啟動比例越高,很好理解。

流程

我們先看圖:

請添加圖片描述

以下是APP啟動的底層細節鏈

點擊APP啟動 -> 加載libSystem -> Runtime注冊回調函數 -> 加載image(鏡像文件) -> 執行map_images和load_images方法 -> 調用main函數。

而其中打開app直到調用main函數之前,就是上圖的dyld工作階段,即pre-main階段的一部分。

  • 進程創建與環境準備(內核 + SpringBoard)

    • 創建進程、啟用 ASLR、加載簽名與沙箱、校驗權限與 Entitlements

    • 解析 Info.plist 的關鍵鍵(Bundle 標識、支持架構/平臺、啟動場景配置等)

    • 系統根據 LaunchScreen.storyboard/舊版 LaunchImage 在“你的代碼尚未執行”時顯示啟動畫面(閃屏由系統渲染,非你主動繪制)

  • dyld 加載與 Mach-O 修復

    • 加載可執行文件及其依賴的動態庫(如 libSystem.B.dylib、libobjc.A.dylib、Swift runtime 等)

    • Rebase:把鏡像內的指針按 ASLR 偏移修正到“真實地址”

    • Bind:為外部符號(如 NSLog)在運行時綁定到實際實現地址

(dyld3/共享緩存會顯著縮短 dylib 加載與綁定時間)

  • 運行時初始化與預執行階段(均在 main 之前)

    • ObjC Runtime 初始化:注冊類、元類、選擇子、協議,處理 Category

    • 調度并執行 Objective?C +load(父類→子類,類→分類;各類/分類各執行一次)

    • 執行 C/C++ 全局構造與帶 __ attribute__((constructor)) 的函數(均為“進程初始化時機”,具體與 +load 的跨語言相對順序不應依賴)

  • 進入程序主入口與應用生命周期

    • 調用 main() → UIApplicationMain(…)

    • 創建 UIApplication 實例,建立主 RunLoop

    • 讀取 Info.plist 中的主界面/場景配置:iOS 13+ 走 UIScene,舊版讀取 UIApplicationMainStoryboardFile

    • AppDelegate 回調:

      • application:willFinishLaunchingWithOptions:(可選)

      • application:didFinishLaunchingWithOptions:(常用,創建 UIWindow、設 rootVC、makeKeyAndVisible())

    • 首個 UIViewController 生命周期:loadView → viewDidLoad → viewWillAppear → 動畫 → viewDidAppear

    • 系統移除啟動畫面,展示首屏

rebase(偏移修正):任何一個app生成的二進制文件,在二進制文件內部所有的方法、函數調用,都有一個地址,這個地址是在當前二進制文件中的偏移地址。一旦在運行時刻(即運行到內存中),每次系統都會隨機分配一個ASLR(Address Space Layout Randomization,地址空間布局隨機化)地址值(是一個安全機制,會分配一個隨機的數值,插入在二進制文件的開頭),例如,二進制文件中有一個 test方法,偏移值是0x0001,而隨機分配的ASLR是0x1f00,如果想訪問test方法,其內存地址(即真實地址)變為 ASLR+偏移值 = 運行時確定的內存地址(即0x1f00+0x0001 = 0x1f01)。程序每次啟動后地址都會隨機變化,這樣程序里所有的代碼地址都需要需要重新對進行計算修復才能正常訪問。rebasing這一步主要就是調整鏡像內部指針的指向。

binding(綁定):,例如NSLog方法,在編譯時期生成的mach-o文件中,會創建一個符號!NSLog(目前指向一個隨機的地址),然后在運行時(從磁盤加載到內存中,是一個鏡像文件),會將真正的地址給符號(即在內存中將地址與符號進行綁定,是dyld做的,也稱為動態庫符號綁定),一句話概括:綁定就是給符號賦值的過程

缺頁錯誤

程序運能運行時因為存在物理內存,也就是說程序加入到物理內存中才得以運行,這一步就是虛擬內存映射到物理內存。這個過程是個使用懶加載方式完成系統到CPU的交互(翻譯)的過程。

而這個過程因為懶加載映射方式的緣故,它是“有多少拿多少”,所以我們會通過一頁一頁的方式也就是page的方式去加載的,iOS的頁的大小是16kb,而macOS是4kb。
也是因為是懶加載的方式,所以如果需要用到的時候發現物理內存中沒有,就會報出“page fault”的缺頁錯誤,然后缺的頁會再加載放入物理內存。這個過程很短,可能30ms,也可能是10ms。

主要階段

主要分為兩個階段:pre-main階段和main階段

pre-main階段:程序啟動到main函數執行前

main階段:在執行main函數后,調用AppDelegate中的-application:didFinishLaunchingWithOptions:方法完成初始化,并展示首頁

pre-main階段

pre-main階段做的事情與dyld的版本有關,此處以dyld2為例。

  • 加載應用的可執行文件
  • 加載動態鏈接庫加載器dyld(dynamic loader)。
  • dyld遞歸加載應用所有依賴的dylib(dynamic library 動態鏈接庫)。
  • 進行**rebase指針調整和bind**符號綁定。
  • ObjCruntime初始化(ObjC setup):ObjC相關Class的注冊、category注冊、selector唯一性檢查等。
  • 初始化(Initializers):執行+load()方法、用attribute((constructor))修飾的函數的調用、創建C++靜態全局變量等。

dyld流程概述:

我們看這個流程是為了看APP啟動到main函數前,也就是dyld是如何將images(鏡像文件:如動靜態庫等)鏈接到內存中去的。而在objc_init的時候是做了什么操作去調起dyld,以及dyld又如何回調至objc中。

加載鏈接庫

從主執行文件的 header 獲取到需要加載的所依賴動態庫列表,而 header 早就被內核映射過。然后它需要找到每個 dylib,然后打開文件讀取文件起始位置,確保它是 Mach-O 文件。接著會找到代碼簽名并將其注冊到內核。然后在 dylib 文件的每個 segment 上調用 mmap()。應用所依賴的 dylib 文件可能會再依賴其他 dylib,所以 dyld所需要加載的是動態庫列表一個遞歸依賴的集合。一般應用會加載 100400dylib 文件,但大部分都是系統 dylib,它們會被預先計算和緩存起來,加載速度很快。

修正

在加載所有的動態鏈接庫之后,它們只是處在相互獨立的狀態,需要將它們綁定起來,這就是 Fix-ups。代碼簽名使得我們不能修改指令,那樣就不能讓一個 dylib 的調用另一個 dylib。這時需要加很多間接層。 現代 code-gen 被叫做動態 PIC(Position Independent Code),意味著代碼可以被加載到間接的地址上。當調用發生時,code-gen 實際上會在 __DATA 段中創建一個指向被調用者的指針,然后加載指針并跳轉過去。所以 dyld 做的事情就是修正(fix-up)指針和數據。Fix-up 有兩種類型,rebasingbinding

在執行main函數之前,需要把類的信息注冊到一個全局的Table中。同時,Objective C支持Category,在初始化的時候,也會把Category中的方法注冊到對應的類中,同時會唯一Selector,這也是為什么當你的Cagegory實現了類中同名的方法后,類中的方法會被覆蓋。

另外,由于iOS開發時基于Cocoa Touch的,所以絕大多數的類起始都是系統類,所以大多數的Runtime初始化起始在Rebase和Bind中已經完成。

初始化

調用+load方法和C/C++靜態初始化對象和標記為 __ attribute__(constructor)的方法

到此結束dyld2的流程

我們再來看一下dyld2與dyld3的區別

請添加圖片描述

dyld2是純粹的in-process,也就是在程序進程內執行的,也就意味著只有當應用程序被啟動的時候,dyld2才能開始執行任務

dyld3則是部分out-of-process,部分in-process。圖中,虛線之上的部分是out-of-process的,在App下載安裝和版本更新的時候會去執行,out-of-process會做如下事情

  • 分析Mach-o Headers
  • 分析依賴的動態庫
  • 查找需要Rebase & Bind之類的符號
  • 把上述結果寫入緩存

此時,在進程內就只需要讀取這個closure(閉包)直接從緩存中讀取數據,大大減少了加載時間。

dyld是在是在dyld::_main函數中調用的

main階段

  • 執行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions 初始化Window
  • 初始化基礎的ViewController結構(一般是UINavigationController+UITabViewController) 獲取數據(Local DB/Network),展示給用戶。

啟動優化

了解app啟動流程和主要階段,我們就可以來進行啟動的優化了。

pre-main

在pre-main階段,這個階段最主要的就是dyld的加載。

而dylibs啟動的第一步就是加載動態庫,加載系統的動態庫使很快的,因為可以緩存,而加載內嵌的動態庫速度較慢。所以,提高這一步的效率的關鍵是:減少動態庫的數量。

我們還可以考慮合并動態庫,但是個人開發就不建議了

Rebase & Bind & Objective C Runtime

Rebase和Bind都是為了解決指針引用的問題。對于Objective C開發來說,主要的時間消耗在Class/Method的符號加載上,所以常見的優化方案是:

  • 減少__DATA段中的指針數量。
  • 合并Category和功能類似的類。比如:UIView+Frame,UIView+AutoLayout…合并為一個
  • 刪除無用的方法和類。
  • 用initialize替代load
  • 減少__atribute__((constructor))的使用,而是在第一次訪問的時候才用dispatch_once等方式初始化。
  • 不要創建線程

main階段

延遲初始化那些不必要的UIViewController

能延遲執行的就延遲執行。比如SDK的初始化,界面的創建。 不能延遲執行的,盡量放到后臺執行。比如數據讀取,原始JSON數據轉對象,日志發送。

啟動優化總結

  • pre?main(dyld/Runtime)

    • 減少/合并第三方與自家動態庫數量;能靜態鏈就靜態鏈(SPM/靜態 XCFramework)。

    • 避免在 +load / 構造器做耗時:移至懶加載(首次使用)或 didFinishLaunching 后的異步。

    • 精簡 ObjC 元數據:刪除無用類/方法/Category,合并零碎 Category(權衡維護性)。

    • 減少符號/指針膨脹:避免過度 @objc 暴露與反射;Swift 盡量 final/struct,降低動態性。

    • 盡量避免啟動期方法交換(swizzle);必須 swizzle 的延后到需要的子系統啟用時。

  • main 之后(App/Scene)

    • 輕量化首屏:小根 VC,延遲創建次級控制器與大型視圖樹。

    • 避免主線程同步 I/O/網絡/大 JSON 解析;放后臺隊列,首幀后再做。

      • SDK 延遲初始化;按需加載功能模塊。
    • 使用 Auto Layout 時約束閉環、減少首幀布局抖動;首屏資源(字體/圖片)盡量小并就近。

流程總結

從用戶點擊 App 圖標開始,系統先創建進程,按 Info.plist 做基礎配置校驗,建立沙箱,并用 LaunchScreen.storyboard 顯示啟動畫面。接著進入 dyld 階段:把可執行文件和依賴的動態庫從共享緩存加載進來,做 ASLR 的 rebase 和符號 binding。隨后運行時初始化:Objective?C/Swift Runtime 注冊類與分類,執行 C/C++ 全局構造器和 ObjC 的 +load(這些都在 main 之前完成)。

然后進入 main,調用 UIApplicationMain 創建 UIApplication,啟動主 RunLoop。iOS 13 及以后通常走 UIScene:在 scene:willConnectTo: 里配置 window 和 rootViewController;老版本是在 application:didFinishLaunching 里創建 window 并 makeKeyAndVisible。接著首個控制器會依次觸發 loadView、viewDidLoad、viewWillAppear,動畫結束后 viewDidAppear,系統移除啟動圖,首幀呈現給用戶。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93470.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93470.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93470.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

知名車企門戶漏洞或致攻擊者遠程解鎖汽車并竊取數據

漏洞概況一家大型汽車制造商的在線系統存在安全漏洞,可能導致客戶數據泄露,并允許攻擊者遠程訪問車輛。該漏洞由安全研究員Eaton Zveare發現,他已于2025年2月向涉事車企報告并促使漏洞修復。Zveare雖未公開車企名稱,但透露這是在美…

Elasticsearch JS 自定義 ConnectionPool / Connection / Serializer、敏感信息脫敏與 v8 平滑遷移

0. 什么時候該用“高階配置”? 復雜網絡/路由需求:自定義“健康節點”判定、權重路由、多租戶隔離。替換 HTTP 棧:接入企業內網網關、打通自研代理/審計、細化超時/連接細節。序列化治理:為超大 JSON、Bulk、查詢串做定制編碼/壓縮…

希爾排序專欄

在排序算法的大家庭中,希爾排序(Shell Sort)以其獨特的 "分組插入" 思想占據著重要地位。它是對插入排序的創造性改進,通過引入 "增量分組" 策略,大幅提升了排序效率。本文將帶你深入理解希爾排序…

Android 歐盟網絡安全EN18031 要求對應的基本表格填寫

Android 歐盟網絡安全EN18031 要求對應的基本表格填寫 文章目錄Android 歐盟網絡安全EN18031 要求對應的基本表格填寫一、背景二、18031認證預填表格三、其他1、Android EN 18031 要求對應的基本表格小結2、EN 18031的要求表格內容填寫3、一定要做三方認證?4、歐盟網…

《Attention-driven GUI Grounding》論文精讀筆記

論文鏈接:[2412.10840] Attention-driven GUI Grounding: Leveraging Pretrained Multimodal Large Language Models without Fine-Tuning 摘要 近年來,多模態大型語言模型(Multimodal Large Language Models,MLLMs)的…

PIDGenRc函數中lpstrRpc的由來和InitializePidVariables函數的關系

第一部分:./base/ntsetup/syssetup/setupp.h:404:#define MAX_PID30_RPC 5BOOL InitializePidVariables() {//// Get the Pid from HKEY_LOCAL_MACHINE\SYSTEM\Setup\Pid//Error RegOpenKeyEx( HKEY_LOCAL_MACHINE,((MiniSetup || OobeSetup) ? szFinalPidKeyNa…

Nginx學習筆記(七)——Nginx負載均衡

?? Nginx學習筆記(七)——Nginx負載均衡 📌 一、負載均衡核心概念 架構定位: #mermaid-svg-00aCvwmJ40DHNd66 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-00aC…

MQ積壓如何處理

處理消息隊列(MQ)積壓是一個需要系統化分析的運維挑戰。下面我將結合常見原因,分步驟說明處理方案,并區分應急措施和根本解決方案:?一、快速診斷積壓原因(核心!)???監控告警分析…

Unity與OpenGL中的材質系統詳解

引言 在現代3D圖形開發中,材質是定義物體外觀的核心元素。Unity引擎提供了強大且直觀的材質系統,使得開發者能夠輕松實現復雜的視覺效果。然而,對于熟悉OpenGL的開發者來說,理解Unity材質系統的工作原理以及如何在OpenGL中實現類…

k8s安裝DragonflyDB取代redis

數據庫類型線程模型吞吐量 (QPS)延遲 (μs)內存效率適用場景兼容性Memcached純內存鍵值存儲多線程100K - 500K10 - 100高緩存、會話存儲無原生密碼認證DragonflyDB多協議內存數據庫多線程1M50 - 200中高高吞吐緩存、Redis 替代兼容 RedisKeyDBRedis 多線程分支多線程500K - 1M5…

Horse3D游戲引擎研發筆記(五):在QtOpenGL環境下,仿three.js的BufferGeometry管理VAO和EBO繪制四邊形

一、背景介紹 在三維圖形渲染中,幾何形狀的管理是引擎的核心功能之一。Three.js通過BufferGeometry接口實現了對頂點數據和索引數據的高效管理,而OpenGL則通過頂點數組對象(VAO)和元素數組對象(EBO)來實現…

Ping32 與 IP-GUARD 深度對比:Ping32,引領企業數據安全新方向

在數字化時代,企業數據宛如珍貴的寶藏,是推動業務發展、保持競爭優勢的核心資產。但與此同時,數據安全威脅也如影隨形,內部員工的誤操作、惡意竊取,外部黑客的攻擊,都可能讓企業數據面臨泄露風險&#xff0…

洛谷 P2842 紙幣問題 1 -普及-

題目描述 某國有 nnn 種紙幣,每種紙幣面額為 aia_iai? 并且有無限張,現在要湊出 www 的金額,試問最少用多少張紙幣可以湊出來? 輸入格式 第一行兩個整數 n,wn,wn,w,分別表示紙幣的種數和要湊出的金額。 第二行一行 nn…

第十四節:物理引擎集成:Cannon.js入門

第十四節:物理引擎集成:Cannon.js入門 引言 物理引擎為3D世界注入真實感,讓物體遵循重力、碰撞和動量等物理規律。Cannon.js是Three.js生態中最強大的物理引擎之一,本文將深入解析其核心機制,并通過Vue3實現物理沙盒系…

二十四、Mybatis-基礎操作-刪除(預編譯SQL)

mybatis環境準備概述與注意事項(springboot項目引入三項必要的起步依賴)項目目錄結構mybatis基礎操作-刪除對應EmpMapper(接口)代碼 package com.itheima.mapper;import org.apache.ibatis.annotations.*;Mapper public interface…

JavaScript 核心基礎:類型檢測、DOM 操作與事件處理

JavaScript 作為松散類型語言,掌握類型檢測規則、DOM 元素獲取方式及事件處理邏輯,是寫出健壯代碼的基礎。本文系統梳理 JS 高頻基礎知識點,結合實戰場景解析原理與用法,幫你建立清晰的知識框架。 一、JS 數據類型與類型檢測&…

軟件開發過程中的維護活動

軟件開發過程中的維護活動軟件維護是軟件生命周期中持續時間最長、成本最高的階段,它并非簡單的“修理”,而是一系列旨在延長軟件生命周期、保持其價值和適應性的工程化活動。研究表明,軟件維護成本可占總成本的60%以上。理解并有效管理維護活…

STC8單片機驅動I2C屏幕:實現時間、日期與溫濕度顯示

STC8 單片機驅動 I2C 屏幕:實現時間、日期與溫濕度顯示 在單片機項目中,“數據可視化” 是核心需求之一 —— 將時間、溫濕度等關鍵信息實時顯示在屏幕上,能讓項目更具實用性。本文以STC8 系列單片機為核心,搭配 I2C 接口的 OLED…

基于SpringBoot+Vue的智能消費記賬系統(AI問答、WebSocket即時通訊、Echarts圖形化分析)

🎈系統亮點:AI問答、WebSocket即時通訊、Echarts圖形化分析;一.系統開發工具與環境搭建1.系統設計開發工具后端使用Java編程語言的Spring boot框架 項目架構:B/S架構 運行環境:win10/win11、jdk17前端: 技術…

[論文筆記] WiscKey: Separating Keys from Values in SSD-Conscious Storage

閱讀 WiscKey 論文時隨手記錄一些筆記。 這篇論文的核心思想理解起來還是很簡單的,但是具體涉及到實現還有一些想不明白的地方,后來看到 TiKV 的 Titan 實現也很有趣,索性把這些問題都記錄下來并拋出來。 本文中和論文相關的內容&#xff0…