didFinishLaunching 與「主線程首次 idle」, 哪個是更優的啟動結束時間點 ?

結論先行

在這兩個候選時間點里——

  1. application:didFinishLaunchingWithOptions: 執行結束
  2. 主線程第一次進入 idle(RunLoop kCFRunLoopBeforeWaiting

若你只能二選一,以「主線程首次 idle」作為 啟動結束 更合理。它比 didFinishLaunchingWithOptions: 更貼近用戶真正“看到并可操作界面”的時刻,而誤差仍可控制;同時埋點方案也比較穩健,可跨 UIKit / SwiftUI / SceneDelegate 使用。


為什么 didFinishLaunching… 偏早?

  • Apple 的 MXAppLaunchMetric 只統計到 didFinishLaunch()(即 didFinishLaunching…)為止,Uber 等大型團隊實測后認為這比他們舊的 首幀渲染 指標“縮水”了一截,需要再補一段自定義測量來涵蓋 UI 繪制流程citeturn10view0。
  • didFinishLaunching… 返回時,UI 還沒走完 viewDidLoad → viewWillAppear → first draw → viewDidAppear,因此用戶仍在看 LaunchScreen 或白屏。把它當作結束點會 樂觀低估 啟動耗時。

為什么選 “RunLoop 首次 idle” 更合適?

維度didFinishLaunching…RunLoop 首次 idle
用戶可見性UI 尚未出現首幀已提交,布局/動畫基本結束
度量一致性受初始化代碼多少影響大對業務初始化代碼魯棒;只要主線程不被長任務阻塞,就在首個空閑循環采樣
實現復雜度最簡單略復雜,需要 CFRunLoopObserver
與 Apple 報表對齊? 對齊 MXAppLaunchMetric需要自行上報,與官方報表有 10?50 ms 左右偏移

RunLoop idle 發生點kCFRunLoopBeforeWaiting——系統即將把主線程睡眠,表明當前循環所有 Timer、Source、Layout、展示任務都完成了,首次出現通常緊跟在根 VC 的 viewDidAppear: 之后citeturn13view0。


實戰埋點模板(一次性)

/// 在 didFinishLaunching… 末尾安裝一次觀察者
static void InstallFirstIdleObserver(void) {CFRunLoopRef rl = CFRunLoopGetMain();static CFRunLoopObserverRef sObs;CFOptionFlags activities = kCFRunLoopBeforeWaiting;   // 首次 idlesObs = CFRunLoopObserverCreateWithHandler(NULL, activities, /*repeat*/false, 0, ^(CFRunLoopObserverRef obs, CFRunLoopActivity act) {uint64_t t = mach_absolute_time() - g_appStartTime;[APMLogger recordColdLaunchIdle:t];               // 自定義上報CFRunLoopRemoveObserver(rl, obs, kCFRunLoopDefaultMode);CFRelease(obs);});CFRunLoopAddObserver(rl, sObs, kCFRunLoopDefaultMode);
}

要點

  1. g_appStartTimemain() 里用 mach_absolute_time() 采集。
  2. 只監聽一次;如果用戶在冷啟動流程中立即進入后臺可在回前臺時重新安裝。
  3. 避免在 didFinishLaunching… 里做重量級同步任務,否則 idle 會被推遲。

何時仍可選 didFinishLaunching…

  • 無需精細區分 冷 / 預熱 / 熱啟動,只想和 MetricKit 或 Firebase Perf 默認數據保持同一口徑。
  • 業務里真正耗時的初始化都放在 application(_:didFinishLaunching…) 之前(+loadstatic 初始化或主函數),后續 UI 幾乎“秒繪”。

TL;DR

“RunLoop 首次空閑” 把 UI 繪制、布局、動畫都算進啟動耗時,比 didFinishLaunchingWithOptions: 更貼近用戶實際體驗,且仍保持實現簡潔可維護,是多數 APM 場景更合理的 啟動結束 定義。


完整代碼

下面給出一套 Objective?C 冷啟動監控模板,滿足兩點要求:

  1. application:didFinishLaunching… 末尾安裝一次 RunLoop idle 觀察者
  2. 若用戶在冷啟動階段馬上切后臺,再次回前臺時自動重裝觀察者

?? 如需 Swift 版本,可把 C?API 調用 (CFRunLoopObserver…) 直接放到 Swift AppDelegate 中,邏輯完全一致。

// AppDelegate.m
#import "AppDelegate.h"
#import <mach/mach_time.h>// 記錄進程啟動瞬間:在 main() 里做
uint64_t gAppStartTime = 0;
__attribute__((constructor))
static void markProcessStart(void) {gAppStartTime = mach_absolute_time();
}/* ------------ 冷啟動 RunLoop idle 監控核心 ------------ */@interface AppDelegate ()
@end@implementation AppDelegate {CFRunLoopObserverRef _idleObserver;   // 當前安裝的觀察者BOOL _coldLaunchDone;                 // 是否已采集完成
}#pragma mark - Observer 安裝 / 卸載- (void)installIdleObserverIfNeeded {if (_coldLaunchDone || _idleObserver) return; // 已完成或已安裝CFRunLoopRef rl = CFRunLoopGetMain();_idleObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,kCFRunLoopBeforeWaiting,          // RunLoop 即將 idle/*repeats*/ false,                // 只觸發一次0,                                // order^(CFRunLoopObserverRef obs, CFRunLoopActivity act) {uint64_t elapsed = mach_absolute_time() - gAppStartTime;[self reportColdLaunch:elapsed];      // ← 你的上報邏輯_coldLaunchDone = YES;CFRunLoopRemoveObserver(rl, obs, kCFRunLoopDefaultMode);CFRelease(obs);_idleObserver = NULL;});CFRunLoopAddObserver(rl, _idleObserver, kCFRunLoopDefaultMode);
}- (void)cancelIdleObserverIfNeeded {if (_idleObserver) {CFRunLoopRemoveObserver(CFRunLoopGetMain(), _idleObserver, kCFRunLoopDefaultMode);CFRelease(_idleObserver);_idleObserver = NULL;}
}#pragma mark - UIApplicationDelegate- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {// …你的初始化代碼…// 1?? 僅在冷啟動安裝一次觀察者[self installIdleObserverIfNeeded];[self observeAppLifeCycle];   // 訂閱前后臺事件return YES;
}#pragma mark - 前后臺切換處理- (void)observeAppLifeCycle {NSNotificationCenter *nc = NSNotificationCenter.defaultCenter;[nc addObserver:self selector:@selector(_didEnterBackground)name:UIApplicationDidEnterBackgroundNotification object:nil];[nc addObserver:self selector:@selector(_willEnterForeground)name:UIApplicationWillEnterForegroundNotification object:nil];
}- (void)_didEnterBackground {// 2?? 若尚未結束冷啟動,取消當前觀察者if (!_coldLaunchDone) {[self cancelIdleObserverIfNeeded];}
}- (void)_willEnterForeground {// 2?? 回前臺時,若冷啟動仍未完成 → 重新安裝觀察者[self installIdleObserverIfNeeded];
}#pragma mark - 上報- (void)reportColdLaunch:(uint64_t)elapsedMach {// 將 mach 時間轉換為毫秒mach_timebase_info_data_t info;mach_timebase_info(&info);double ms = (double)elapsedMach * info.numer / info.denom / 1e6;NSLog(@"[APM] Cold launch Time?to?Idle = %.1f ms", ms);// 調用你自己的 APM / 埋點上報接口……
}@end

關鍵點說明

位置目的
installIdleObserverIfNeeded在主 RunLoop 進入第一次 idle (kCFRunLoopBeforeWaiting) 時觸發;只安裝一次,防止重復統計。
后臺切換如果在冷啟動尚未結束時收到 DidEnterBackground,先移除觀察者;回到前臺的 WillEnterForeground 再重新安裝,保證最終一定能命中“首次 idle”。
_coldLaunchDone成功記錄后置為 YES,后續不再重復安裝。
mach_absolute_time → 毫秒使用 mach_timebase_info 做單位換算,避免因 CPU 頻率變化帶來的誤差。

如需 Swift 寫法,可用 RunLoop.main.add(_:forMode:)CFRunLoopObserverCreateWithHandler 的橋接版本,或直接使用 CFRunLoopObserverCreate 結合 Unmanaged<AnyObject>.fromOpaque 保存 self;邏輯保持一致即可。

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

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

相關文章

Vue3 + TypeScript中defineEmits 類型定義解析

TypeScript 中 Vue 3 的 defineEmits 函數的類型定義&#xff0c;用于聲明組件可以觸發的事件。以下是分步解釋&#xff1a; 1. 泛型定義 ts <"closeDialog" | "getApplySampleAndItemX"> 作用&#xff1a;定義允許的事件名稱集合&#xff0c;即組…

樹莓派超全系列教程文檔--(34)樹莓派配置GPIO

配置GPIO GPIO控制gpio 文章來源&#xff1a; http://raspberry.dns8844.cn/documentation 原文網址 GPIO控制 gpio 通過 gpio 指令&#xff0c;可以在啟動時將 GPIO 引腳設置為特定模式和值&#xff0c;而以前需要自定義 dt-blob.bin 文件。每一行都對一組引腳應用相同的設…

AladdinEdu(H卡GPU算力平臺)使用教程: 1)注冊與開通流程 2)插件使用流程

一、注冊與開通流程 首先進入AladdinEdu官網&#xff1a;AladdinEdu-同學們用得起的H卡算力平臺-高效做AI就上Aladdin 完成注冊&#xff0c;并進行學生認證&#xff1a;學生認證賬戶&#xff0c;認證期間享受教育優惠價。 登錄官網進入控制臺 二、插件使用流程 VScode中…

精益數據分析(6/126):深入理解精益分析的核心要點

精益數據分析&#xff08;6/126&#xff09;&#xff1a;深入理解精益分析的核心要點 在創業和數據驅動的時代浪潮中&#xff0c;我們都在不斷探索如何更好地利用數據推動業務發展。我希望通過和大家分享對《精益數據分析》的學習心得&#xff0c;一起在這個充滿挑戰和機遇的領…

2.深入剖析 Rust+Axum 類型安全路由系統

摘要 詳細解讀 RustAxum 路由系統的關鍵設計原理&#xff0c;涵蓋基于 Rust 類型系統的路由匹配機制、動態路徑參數與正則表達式驗證以及嵌套路由與模塊化組織等多種特性。 一、引言 在現代 Web 開發中&#xff0c;路由系統是構建 Web 應用的核心組件之一&#xff0c;它負責…

運籌學之模擬退火

目錄 一、歷史二、精髓思想三、案例與代碼實現 一、歷史 問&#xff1a;誰在什么時候提出模擬退火&#xff1f;答&#xff1a;模擬退火算法&#xff08;Simulated Annealing&#xff0c;SA&#xff09;是由斯圖爾特柯爾斯基&#xff08;Scott Kirkpatrick&#xff09; 等人在 …

android測試依賴

Android 項目中常用的測試相關庫 1. androidx.arch.core:core-testing:2.2.0 作用&#xff1a; 提供與 Android Architecture Components&#xff08;如 LiveData、ViewModel&#xff09;相關的測試工具。主要用于測試基于 LiveData 的異步操作。 常見功能&#xff1a; 即時…

stack,queue和priority_queue

1. stack 1.1 stack 的介紹 棧是一種容器適配器&#xff0c;專門設計用于LIFO環境&#xff08;后進先出&#xff09;&#xff0c;其中元素僅從容器的一端插入和提取。 容器適配器&#xff0c;也就是使用特定容器類的封裝對象作為其底層容器&#xff0c;提供一組特定的成員函…

MinnowBoard MAX單板UEFI BIOS代碼編譯教程

此教程用于UEFI EDK2代碼的研究&#xff0c;雖然EDK2框架代碼開源&#xff0c;但是都是在模擬器上跑仿真&#xff0c;差點意思&#xff0c;搞過嵌入式的應該有一個共識&#xff0c;是騾子是馬&#xff0c;你得把板子點亮啊。MinnowBoard MAX單板是intel10多年前發布的軟硬件全部…

AI Transformers 架構體系 權重文件類型 safeterson和gguf格式轉換【2-1】

模型權重文件&#xff1a;存儲訓練好的模型參數,也就是w和b&#xff0c;是模型推理和微調的基礎 .pt、.ckpt、.safetensors、gguf 配置文件&#xff1a;確保模型架構的一致性&#xff0c;使得權重文件能夠正確加載 config.json、generation_config.json 詞匯表文件&#xff1a;…

K8S微服務部署及模擬故障觀測

概述 本文介紹了如何在 Kubernetes (K8S) 集群中部署微服務&#xff0c;并模擬常見的故障場景&#xff08;如 Pod 故障、節點故障、網絡故障&#xff09;以測試系統的容錯能力。通過本實驗&#xff0c;了解 Kubernetes 的自動恢復機制以及如何通過監控和日志分析快速定位和解決…

OpenStack Yoga版安裝筆記(23)Swift安裝

一、官方文檔 Object Storage Install Guide — Swift 2.29.3.dev5 documentation 二、環境準備 之前的實驗&#xff0c;已經有controller, compute1, block1節點&#xff0c;并已經完成Keystone、Glance、Nova、Neutron、Cinder等主要OpenStack Service的安裝。 此處新增…

06-libVLC的視頻播放器:推流RTMP

創建媒體對象 libvlc_media_t* m = libvlc_media_new_path(m_pInstance, inputPath.toStdString().c_str()); if (!m) return -1; // 創建失敗返回錯誤 libvlc_media_new_path:根據文件路徑創建媒體對象。注意:toStdString().c_str() 在Qt中可能存在臨時字符串析構問題,建議…

少兒編程路線規劃

少兒編程路線規劃—一文寫明白 現在有很多的編程機構&#xff0c;五花八門的。我有幸也見識到了大家的營銷策略。這些策略有黑有白吧&#xff0c;從業幾年&#xff0c;沉淀下來一些客戶角度的干貨&#xff0c;分享給大家。 如果是想以很遠很遠的就業為目的&#xff0c;畢業就…

ios app的ipa文件提交最簡單的方法

ipa文件是ios的app打包后生成的二級制文件&#xff0c;在上架app store connect或做testflight測試的時候&#xff0c;它提示我們需要使用xcode、transporter或xcode命令行等方式來上傳。 而xcode、transporter或xcode命令行的安裝都需要使用mac電腦&#xff0c;假如沒有mac電…

怎么查看LLM Transformer 架構進行并行計算和設備映射

怎么查看LLM Transformer 架構進行并行計算和設備映射 num_hidden_layers = model.config.num_hidden_layers print(num_hidden_layers) print(model) LLM(大語言模型)通常是基于 Transformer 架構 構建的,它由多個模塊化的層(Layer)堆疊組成,每個層都有其獨特的作用。…

微信小程序獲得當前城市,獲得當前天氣

// // 獲取用戶當前所在城市 // wx.getLocation({// type: wgs84, // 默認為 wgs84 返回 gps 坐標,gcj02 返回可用于 wx.openLocation 的坐標 // success: function(res) {// console.log(獲取位置成功, res); // // 使用騰訊地圖API進行逆地址解析 // wx…

美國國土安全部終止資助,CVE漏洞數據庫項目面臨停擺危機

&#xff08;圖片來源&#xff1a;Jerome460 / Shutterstock&#xff09; 25年漏洞追蹤體系即將終結 美國非營利研發組織MITRE宣布&#xff0c;其與美國國土安全部&#xff08;DHS&#xff09;簽訂的"通用漏洞披露&#xff08;CVE&#xff09;"數據庫維護合同將于2…

Kafka下載和使用(Windows版)

Apache Kafka 是一個高吞吐量的分布式消息系統&#xff0c;廣泛應用于日志收集、實時流處理等場景。本文將以 Windows 系統為例&#xff0c;詳細介紹 Kafka 的安裝和使用方法。 一、安裝方式 在 Windows 系統上運行 Apache Kafka&#xff0c;通常有兩種方式&#xff1a; 1.W…

RBAC的使用

1、簡述RBAC的作用及工作流程 Rbac基于角色訪問控制&#xff0c;用于管理用戶對集群資源的訪問權限&#xff0c;通過定義角色和綁定規則&#xff0c;將用戶與權限進行關聯&#xff0c;作用&#xff1a;權限精細化管理&#xff0c;操作便捷與統一管理&#xff0c;動態調整權限。…