iOS-APP的啟動流程和生命週期
知 識 點 / 超 人
前言
當自己對技術對APP的效能達到一定的追求時,就需要對APP有較深的瞭解,越是深入的瞭解和理解才能從各個點上去優化效能。深入的理解還可以使我們在對iOS進行逆向工程時更加了解從哪一個時間段什麼方式入侵最合適。
我把APP的生命流程分為三大部分:
1.APP的啟動流程(pre-main)
2.APP的初始化流程(main)
3.APP的執行時生命週期
APP的啟動流程
1.iOS系統首先會載入解析該APP的Info.plist檔案,因為Info.plist檔案中包含了支援APP載入執行所需要的眾多Key,value配置資訊,例如APP的執行條件(Required device capabilities),是否全屏,APP啟動圖資訊等。
2.建立沙盒(iOS8後,每次啟動APP都會生成一個新的沙盒路徑)
3.根據Info.plist的配置檢查相應許可權狀態
4.載入Mach-O檔案讀取dyld路徑並執行dyld動態聯結器(核心載入了主程式,dyld只會負責動態庫的載入)
- 4.1 首先dyld會尋找合適的CPU執行環境
- 4.2 然後載入程式執行所需的依賴庫和我們自己寫的.h.m檔案編譯成的.o可執行檔案,並對這些庫進行連結。
- 4.3 載入所有方法(runtime就是在這個時候被初始化的)
- 4.4 載入C函式
- 4.5 載入category的擴充套件(此時runtime會對所有類結構進行初始化)
- 4.6 載入C++靜態函式,載入OC+load
- 4.7 最後dyld返回main函式地址,main函式被呼叫
Mach-O檔案說明:
Mach-O檔案格式是 OS X 與 iOS 系統上的可執行檔案格式,類似於windows的 PE 檔案。想我們編譯產生的.o檔案、程式可執行檔案和各種庫等都是Mach-O檔案。
Mach-O檔案主要有3部分組成:
1.Header:儲存了一些基本資訊,包括了該檔案執行的平臺、檔案型別、LoadCommands的個數等等。Headers的主要作用就是幫助系統迅速的定位Mach-O檔案的執行環境,檔案型別。儲存了一些dyld重要的載入引數
2.LoadCommands:可以理解為載入命令,在載入Mach-O檔案時會使用這裡的資料來確定記憶體的分佈以及相關的載入命令。比如我們的main函式的載入地址,程式所需的dyld的檔案路徑,以及相關依賴庫的檔案路徑。
3.Data: 每一個segment的具體資料都儲存在這裡,這裡包含了具體的程式碼、資料等等。
安全
ASLR(Address Space Layout Randomization):地址空間佈局隨機化,映象會在隨機的地址上載入。
程式碼簽名:為了在執行時驗證 Mach-O 檔案的簽名,並不是每次重複的去讀入整個檔案,而是把檔案每頁內容都生成一個單獨的加密雜湊值,並把值儲存在 __LINKEDIT 中。這使得檔案每頁的內容都能及時被校驗確並保不被篡改。而不是每個檔案都做hash加密並做數字簽名。
如果要檢視Mach-O檔案可以用Mac OSX自帶的otool
工具,下面是一些常用的檢視Mach-O檔案命令
命令程式碼 | 命令舉例 | 命令說明 |
---|---|---|
otool -f xxx | otool -f LYSDK | 檢視fat headers資訊 |
otool -a xxx | otool -a LYSDK | 檢視archive headers資訊 |
otool -h xxx | otool -h LYSDK | 檢視Mach-O頭結構資訊 |
otool -l xxx | otool -l LYSDK | 檢視load commands資訊 |
otool -f xxx | otool -f LYSDK | 檢視fat headers資訊 |
otool -L xxx | otool -L LYSDK | 檢視依賴的動態庫,包括動態庫名稱、當前版本號、相容版本號 |
otool -D xxx | otool -D LYSDK | 檢視所支援的框架型別 |
otool -t -v xxx | otool -t -v LYSDK | 檢視text section |
otool -d xxx | otool -d LYSDK | 檢視objective-C segment資訊 |
otool -o xxx | otool -o LYSDK | 檢視fat headers資訊 |
otool -I xxx | otool -I LYSDK | 檢視symbol table資訊 |
otool -v -s __TEXT __objc_methname xxx | otool -v -s __TEXT __objc_methname LYSDK | 獲取所有方法名稱 |
下面是對Mach-O中section常見欄位的說明
section | 說明 |
---|---|
__TEXT.__text | 主程式程式碼 |
__TEXT.__cstring | C語言字串 |
__TEXT.__const | const 關鍵字修飾的常量 |
__TEXT.__stubs | 用於 Stub 的佔位程式碼,很多地方稱之為樁程式碼。 |
__TEXT.__stubs_helper | 當 Stub 無法找到真正的符號地址後的最終指向 |
__TEXT.__objc_methname | Objective-C 方法名稱 |
__TEXT.__objc_classname | Objective-C 類名稱 |
__DATA.__data | 初始化過的可變資料 |
__DATA.__la_symbol_ptr | lazy binding 的指標表,表中的指標一開始都指向 __stub_helper |
__DATA.nl_symbol_ptr | 非 lazy binding 的指標表,每個表項中的指標都指向一個在裝載過程中,被動態鏈機器搜尋完成的符號 |
__DATA.__const | 沒有初始化過的常量 |
__DATA.__cfstring | 程式中使用的 Core Foundation 字串(CFStringRefs) |
__DATA.__bss | BSS,存放為初始化的全域性變數,即常說的靜態記憶體分配 |
__DATA.__commont | 沒有初始化過的符號宣告 |
__DATA.__objc_classlist | Objective-C 類列表 |
__DATA.__objc_protolist | bjective-C 原型列表 |
__DATA.__objc_imginfo | Objective-C 映象資訊 |
__DATA.__objc_selfrefs | Objective-C self 引用 |
__DATA.__objc_protorefs | Objective-C 原型引用 |
__DATA.__objc_superrefs | Objective-C 超類引用 |
__TEXT.__objc_methtype | Objective-C 方法型別 |
dyld說明:
dyld叫做動態連結器,主要的職責是完成各種庫的連線。dyld是蘋果用C++寫的一個開源庫,可以在蘋果的git上直接檢視原始碼。
當系統從xnu核心態
把控制權轉交給dyld變成使用者態
後dyld首先初始化程式環境,將可執行檔案以及相應的系統依賴庫與我們自己加入的庫載入進記憶體中,生成對應的ImageLoader類對應的image物件(映象檔案),對這些image進行連結,呼叫各image的初始化方法等等(注:這裡多數情況都是採用的遞迴,從底向上的方法呼叫),其中runtime就是在這個過程中被初始化的,這些事情大多數在dyld:_mian方法中被髮生。
對於動態庫和靜態庫的連結方式是不同的,詳細的大家可以看看我另外一篇關於動態庫與靜態庫的文章
從整個APP啟動流程中,我們可以做優化的點主要有
1.是減少系統依賴庫
2.減少自己需要加入的各種三方庫(庫越少dyld載入的速度越快,就能越早的返回程式入口main函式的地址)
3.有一些自己加入的庫,能選擇靜態庫就選擇靜態庫,少用動態庫,因為動態庫的載入方式比靜態庫慢。如果必須依賴動態庫,則把多個非系統的動態庫合併成一個動態庫。
4.自己加入的各種framework庫根據情況設為optional和required,如果該framework在當前App支援的所有iOS系統版本中都存在,那麼就設為required,否則就設為optional,因為optional會有些額外的檢查會導致載入變慢。
5.將不必須在+load方法中做的事情延遲到+initialize中,儘量不要用C++虛擬函式(建立虛擬函式表有開銷)。
6.減少專案檔案中Category,靜態變數等的使用數量
7.使用appCode檢查專案中,那些類和方法沒有使用到。 把沒有使用到的刪除
8.讓UI大佬儘量把給的資源壓縮到最小,因為在啟動載入時會載入資源圖片進行IO操作。所以圖片小載入速度也會響應提升。
9.記憶體上優化:類和方法名不要太長:iOS每個類和方法名都在__cstring段裡都存了相應的字串值,所以類和方法名的長短也是對可執行檔案大小是有影響,及影響載入速度也消耗記憶體;因為OC的動態特性,都是載入後通過類/方法名反射找到這個類/方法進行呼叫,OC的物件模型會把類/方法名字串都儲存下來(壓縮演算法TinyPNG)。
冷啟動、熱啟動
如果程式剛被執行過一次,那麼程式的程式碼會被dyld快取起來,因此即使殺掉程式再次重啟載入時間也會相對快一點,如果長時間沒有啟動或者當前dyld的快取已經被其他應用佔據,那麼這次啟動所花費的時間就要長一點,這就分別是熱啟動和冷啟動的概念。
啟動時間測試方法
在 Xcode 中 Edit scheme -> Run -> Auguments 將環境變數 DYLD_PRINT_STATISTICS
設為 1。隨後啟動APP時控制檯會輸出的啟動耗時內容。
APP的初始化流程
- 1.main 函式
- 2.執行UIApplicationMain
- 2.1 建立UIApplication物件
- 2.2 建立UIApplication的delegate物件
- 2.3 建立MainRunloop
- 2.4 delegate物件開始處理(監聽)系統事件(沒有storyboard)
- 3.根據Info.plist獲得最主要storyboard的檔名,載入最主要的storyboard(有storyboard)
- 程式啟動完畢的時候, 就會呼叫代理的application:didFinishLaunchingWithOptions:方法
在application:didFinishLaunchingWithOptions:中建立UIWindow
建立和設定UIWindow的rootViewController
- 最終顯示第一個視窗
main.m檔案說明
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
//main函式是整個程式的入口
int main(int argc, char * argv[]) {
//引數argc說明:命令列總的引數個數。
//引數argv說明:是引數的陣列,argv中第一個引數為app的路徑+全名。
printf("argc = %d\n", argc);
char *argChar = argv[0];
printf("index = %i ,argv = %s\n", 0, argChar);
@autoreleasepool {
//UIApplicationMain函式說明
//第一個引數argc:引數是main函式C語言中傳入的,保持與main函式相同。
//第二個引數argv:同argc引數一樣
//第三個引數nil:該引數為principalClassName (主要類名)
// 如果principalClassName是nil,那麼它的值將從Info.plist去獲取,如果Info.plist沒有,則預設為UIApplication。
// principalClass這個類除了管理整個程式的生命週期之外什麼都不做,它只負責監聽事件然後交給delegateClass去做。
//第四個引數NSStringFromClass([AppDelegate class]):委託代理類的類名,UIApplication建立的delegate物件的類名
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
UIApplication代理方法說明:
//app啟動完畢後就會呼叫
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//列印方法名
//本地通知的Key,UIApplicationDidFinishLaunchingNotification
NSLog(@"--- %s ---",__func__);
}
//比如當有電話進來或簡訊進來或鎖屏等情況下,這時應用程式掛起進入非活動狀態。
//也就是手機介面還是顯示著你當前的應用程式的視窗,只不過被別的任務強制佔用了,也可能是即將進入後臺狀態(因為要先進入非活動狀態然後才會進入後臺狀態)
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(@"--- %s ---",__func__);
}
//指當前視窗不是你的App,大多數程式進入這個後臺會在這個狀態上停留一會,時間到之後會進入掛起狀態(Suspended)。
//如果你程式特殊處理後可以長期處於後臺狀態也可以執行。
//Suspended (掛起): 程式在後臺不能執行程式碼。系統會自動把程式變成這個狀態而且不會發出通知。
//當掛起時,程式還是停留在記憶體中的,當系統記憶體低時,系統就把掛起的程式清除掉,為前臺程式提供更多的記憶體
- (void)applicationDidEnterBackground:(UIApplication *)application
{
/本地通知的Key,/UIApplicationDidEnterBackgroundNotification
NSLog(@"--- %s ---",__func__);
//當使用者按下home鍵後,程式進入後臺執行狀態,如果記憶體不足被系統關閉或者使用者手動殺掉程式,都不會呼叫applicationWillTerminate函式。
//在程式進入後臺時,新增一beginBackgroundTaskWithExpirationHandler(後臺執行通知函式),程式進入後臺10分鐘內,程式還在執行,並可以響應一些訊息
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"程式關閉");
}];
}
//app程式程式從後臺回到前臺就會呼叫
- (void)applicationWillEnterForeground:(UIApplication *)application
{
//本地通知的Key,UIApplicationWillEnterForegroundNotification
NSLog(@"--- %s ---",__func__);
}
//app程式獲取焦點就會呼叫
- (void)applicationDidBecomeActive:(UIApplication *)application
{
//本地通知的Key,UIApplicationDidBecomeActiveNotification
NSLog(@"--- %s ---",__func__);
}
// 記憶體警告,可能要終止程式,清除不需要再使用的記憶體
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
NSLog(@"--- %s ---",__func__);
}
// 程式即將退出呼叫
- (void)applicationWillTerminate:(UIApplication *)application
{
//UIApplicationWillTerminateNotification
NSLog(@"--- %s ---",__func__);
}
APP初始化的UIApplication呼叫順序為:
1.application:didFinishLaunchingWithOptions:
2.applicationDidBecomeActive:
APP初始化流程的優化點
1.儘量使用純程式碼而不是xib或者storyboard來進行UI框架的搭建,尤其是使用的TabBarController這種,儘量避免使用xib和storyboard,因為xib和storyboard也還是要解析成程式碼來渲染頁面,並且官網為了滿足更多的需求,必定做了更多的適配判斷處理,會多很多步驟。會增加程式碼的執行效率從而增加啟動時長。
2.儘量在application:didFinishLaunchingWithOptions:中程式碼的執行時間。能多執行緒就多執行緒,能後臺執行就後臺執行。部分載入可以選擇懶載入或者後臺載入。不要阻塞主執行緒從而造成啟動時間加長。
生命週期
ViewController的生命週期方法說明:(詳細說明都在程式碼註釋中)
#pragma mark --- sb相關的life circle
//執行順序1
// 當使用storyBoard時走的第一個方法。這個方法而不走initWithNibName方法。
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSLog(@"%s", __func__);
if (self = [super initWithCoder:aDecoder])
{
//這裡僅僅是建立self,還沒有建立self.view所以不要在這裡設定self.view相關操作
}
return self;
}
#pragma mark --- life circle
//執行順序1
// 當控制器不是SB時,都走這個方法。(xib或純程式碼都會走這個方法)
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
NSLog(@"%s", __func__);
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
//這裡僅僅是建立self,還沒有建立self.view所以不要在這裡設定self.view相關操作
}
return self;
}
//執行順序2
// xib載入完成時呼叫,純程式碼不會呼叫。系統自行呼叫
- (void)awakeFromNib {
[super awakeFromNib];
//當awakeFromNib方法被呼叫時,所有檢視的outlet和action已經連線,但還沒有被確定。
NSLog(@"%s", __func__);
}
//執行順序3
// 載入控制器的self.view檢視。(預設從nib)
- (void)loadView {
//該方法一般開發者不主動呼叫,應該由系統自行呼叫。
//系統會在self.view為nil的時候呼叫。當控制器生命週期到達需要呼叫self.view的時候會自行呼叫。
//或者當我們設定self.view=nil後,下次需要用到self.view時,系統發現self.view為nil,則會呼叫該方法。
//該方法一般會首先根據nibName去找對應的nib檔案然後載入。
//如果nibName為空或找不到對應的nib檔案,則會建立一個空檢視(這種情況一般是純程式碼)
NSLog(@"%s", __func__);
//該方法比較特殊,如果重寫不能呼叫父類的方法[super loadView];
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
}
//執行順序4
//檢視控制器中的檢視載入完成,viewController自帶的view載入完成後會第一個呼叫的方法
- (void)viewDidLoad {
//當self.view被建立後,會立即呼叫該方法。一般用於完成各種初始化操作
NSLog(@"%s", __func__);
[super viewDidLoad];
}
//執行順序5
//檢視將要出現
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewWillAppear:animated];
}
//執行順序6
// view 即將佈局其 Subviews
- (void)viewWillLayoutSubviews {
//view即將佈局它的Subviews子檢視。 當view的的屬性發生了改變。
//需要要調整view的Subviews子檢視的位置,在調整之前要做的工作都可以放在該方法中實現
NSLog(@"%s", __func__);
[super viewWillLayoutSubviews];
}
//執行順序7
// view 已經佈局其 Subviews
- (void)viewDidLayoutSubviews {
//view已經佈局其Subviews,這裡可以放置調整完成之後需要做的工作
NSLog(@"%s", __func__);
[super viewDidLayoutSubviews];
}
//執行順序8
//檢視已經出現
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewDidAppear:animated];
}
//執行順序9
//檢視將要消失
- (void)viewWillDisappear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewWillDisappear:animated];
}
//執行順序10
//檢視已經消失
- (void)viewDidDisappear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewDidDisappear:animated];
}
//執行順序11
// 檢視被銷燬
- (void)dealloc {
//系統會在此時釋放掉init與viewDidLoad中建立的物件
NSLog(@"%s", __func__);
}
//執行順序12
//出現記憶體警告 //模擬記憶體警告:點選模擬器->hardware-> Simulate Memory Warning
- (void)didReceiveMemoryWarning {
//在記憶體足夠的情況下,app的檢視通常會一直儲存在記憶體中,但是如果記憶體不夠,一些沒有正在顯示的viewController就會收到記憶體不足的警告。
//然後就會釋放自己擁有的檢視,以達到釋放記憶體的目的。但是系統只會釋放記憶體,並不會釋放物件的所有權,所以通常我們需要在這裡將不需要顯示在記憶體中保留的物件釋放它的所有權,將其指標置nil。
NSLog(@"%s", __func__);
[super didReceiveMemoryWarning];
}
補充說明:
- 1.main函式中第二個引數
char * argv[]
的完整內容列印
argc = 1
index = 0 ,argv = /Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/Containers/Bundle/Application/F69AC7F5-8494-4D8A-90CA-233EEB58ED65/Demo.app/Demo
index = 1 ,argv = (null)
index = 2 ,argv = DYLD_FRAMEWORK_PATH=/Users/xieyujia/Library/Developer/Xcode/DerivedData/Demo-dioljezkbtpqsvhawqbhwszitdtu/Build/Products/Debug-iphonesimulator
index = 3 ,argv = TMPDIR=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/Containers/Data/Application/562BCE9C-E0ED-4867-A277-C30C9E55F541/tmp
index = 4 ,argv = CA_DEBUG_TRANSACTIONS=0
index = 5 ,argv = SQLITE_ENABLE_THREAD_ASSERTIONS=1
index = 6 ,argv = HOME=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/Containers/Data/Application/562BCE9C-E0ED-4867-A277-C30C9E55F541
index = 7 ,argv = __XPC_DYLD_FRAMEWORK_PATH=/Users/xieyujia/Library/Developer/Xcode/DerivedData/Demo-dioljezkbtpqsvhawqbhwszitdtu/Build/Products/Debug-iphonesimulator
index = 8 ,argv = OS_ACTIVITY_DT_MODE=YES
index = 9 ,argv = SIMULATOR_VERSION_INFO=CoreSimulator 581.2 - Device: iPhone 8 - Runtime: iOS 12.1 (16B91) - DeviceType: iPhone 8
index = 10 ,argv = SIMULATOR_UDID=0BB9BC41-490F-40AA-BCC1-94D98023D80F
index = 11 ,argv = SIMULATOR_MAINSCREEN_SCALE=2.000000
index = 12 ,argv = SIMULATOR_EXTENDED_DISPLAY_PROPERTIES=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/Library/Application Support/Simulator/extended_display.plist
index = 13 ,argv = SIMULATOR_DEVICE_NAME=iPhone 8
index = 14 ,argv = SIMULATOR_AUDIO_SETTINGS_PATH=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/var/run/simulatoraudio/audiosettings.plist
index = 15 ,argv = CFFIXED_USER_HOME=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/Containers/Data/Application/562BCE9C-E0ED-4867-A277-C30C9E55F541
index = 16 ,argv = DYLD_FALLBACK_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib
index = 17 ,argv = SIMULATOR_RUNTIME_VERSION=12.1
index = 18 ,argv = SIMULATOR_PRODUCT_CLASS=D20
index = 19 ,argv = SIMULATOR_MODEL_IDENTIFIER=iPhone10,4
index = 20 ,argv = SIMULATOR_MAINSCREEN_WIDTH=750
index = 21 ,argv = SIMULATOR_MAINSCREEN_PITCH=326.000000
index = 22 ,argv = SIMULATOR_LEGACY_ASSET_SUFFIX=iphone
index = 23 ,argv = SIMULATOR_CAPABILITIES=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/DeviceTypes/iPhone 8.simdevicetype/Contents/Resources/capabilities.plist
index = 24 ,argv = SIMULATOR_BOOT_TIME=1546929769
index = 25 ,argv = IPHONE_TVOUT_EXTENDED_PROPERTIES=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/Library/Application Support/Simulator/extended_display.plist
index = 26 ,argv = IPHONE_SHARED_RESOURCES_DIRECTORY=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data
index = 27 ,argv = __XPC_DYLD_LIBRARY_PATH=/Users/xieyujia/Library/Developer/Xcode/DerivedData/Demo-dioljezkbtpqsvhawqbhwszitdtu/Build/Products/Debug-iphonesimulator
index = 28 ,argv = NSUnbufferedIO=YES
index = 29 ,argv = CUPS_SERVER=/private/tmp/com.apple.launchd.bb2Qmc8rFq/Listeners
index = 30 ,argv = SIMULATOR_ROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot
index = 31 ,argv = SIMULATOR_HOST_HOME=/Users/xieyujia
index = 32 ,argv = PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/local/bin
index = 33 ,argv = DYLD_ROOT_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot
index = 34 ,argv = XPC_SERVICE_NAME=UIKitApplication:heyujia.demo01[0xff86][3399]
index = 35 ,argv = DYLD_FALLBACK_FRAMEWORK_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks
index = 36 ,argv = __XCODE_BUILT_PRODUCTS_DIR_PATHS=/Users/xieyujia/Library/Developer/Xcode/DerivedData/Demo-dioljezkbtpqsvhawqbhwszitdtu/Build/Products/Debug-iphonesimulator
index = 37 ,argv = RWI_LISTEN_SOCKET=/private/tmp/com.apple.launchd.ZoJuzmdhwC/com.apple.webinspectord_sim.socket
index = 38 ,argv = CLASSIC=0
index = 39 ,argv = SIMULATOR_RUNTIME_BUILD_VERSION=16B91
index = 40 ,argv = SIMULATOR_LOG_ROOT=/Users/xieyujia/Library/Logs/CoreSimulator/0BB9BC41-490F-40AA-BCC1-94D98023D80F
index = 41 ,argv = SIMULATOR_AUDIO_DEVICES_PLIST_PATH=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/var/run/com.apple.coresimulator.audio.plist
index = 42 ,argv = IOS_SIMULATOR_SYSLOG_SOCKET=/tmp/com.apple.CoreSimulator.SimDevice.0BB9BC41-490F-40AA-BCC1-94D98023D80F/syslogsock
index = 43 ,argv = TESTMANAGERD_SIM_SOCK=/private/tmp/com.apple.launchd.q0OJSNTn60/com.apple.testmanagerd.unix-domain.socket
index = 44 ,argv = SIMULATOR_MEMORY_WARNINGS=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/var/run/memory_warning_simulation
index = 45 ,argv = SIMULATOR_MAINSCREEN_HEIGHT=1334
index = 46 ,argv = SIMULATOR_HID_SYSTEM_MANAGER=/Library/Developer/PrivateFrameworks/CoreSimulator.framework/Resources/Platforms/iphoneos/Library/Frameworks/SimulatorHID.framework
index = 47 ,argv = IPHONE_SIMULATOR_ROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot
index = 48 ,argv = XPC_FLAGS=0x0
index = 49 ,argv = DYLD_LIBRARY_PATH=/Users/xieyujia/Library/Developer/Xcode/DerivedData/Demo-dioljezkbtpqsvhawqbhwszitdtu/Build/Products/Debug-iphonesimulator:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/introspection
index = 50 ,argv = CA_ASSERT_MAIN_THREAD_TRANSACTIONS=0
index = 51 ,argv = XPC_SIMULATOR_LAUNCHD_NAME=com.apple.CoreSimulator.SimDevice.0BB9BC41-490F-40AA-BCC1-94D98023D80F
index = 52 ,argv = SIMULATOR_SHARED_RESOURCES_DIRECTORY=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data
index = 53 ,argv = (null)
index = 54 ,argv = (null)
index = 55 ,argv = executable_path=/Users/xieyujia/Library/Developer/CoreSimulator/Devices/0BB9BC41-490F-40AA-BCC1-94D98023D80F/data/Containers/Bundle/Application/F69AC7F5-8494-4D8A-90CA-233EEB58ED65/Demo.app/Demo
index = 56 ,argv = pfz=0x7ffffffd7000
index = 57 ,argv = MallocNanoZone=1
index = 58 ,argv =
index = 59 ,argv =
index = 60 ,argv = ptr_munge=
index = 61 ,argv = main_stack=
index = 62 ,argv = executable_file=0x1901000004,0x201c53e18
index = 63 ,argv = dyld_file=0x1901000004,0x201c06c45
index = 64 ,argv = executable_cdhash=2df67be51d7044e5d63c4df3d6e326b89880721c
index = 65 ,argv = (null)
- 2.如果要設定啟動頁面顯示的時長,可以在application:didFinishLaunchingWithOptions方法中
[NSThread sleepForTimeInterval:2];
來阻塞執行緒達到啟動頁面倒數計時。
參考文獻
相關文章
- Spring容器啟動流程+Bean的生命週期【附原始碼】SpringBean原始碼
- Activity生命週期與啟動模式模式
- 051 生命週期銷燬流程
- 品牌生命週期和產品生命週期之間的關係
- Android入門教程之Activity(生命週期,啟動...)Android
- 用生命週期規範元件化流程元件化
- React 渲染 和 生命週期React
- View生命週期與Activity生命週期的關係View
- thinkphp6框架執行流程(生命週期)PHP框架
- 【UniApp】-uni-app-OptionAPI應用生命週期和頁面生命週期APPAPI
- 【UniApp】-uni-app-CompositionAPI應用生命週期和頁面生命週期APPAPI
- docker - 生命週期和狀態Docker
- 生命週期
- 為什麼 bindService 能和 Activity 的生命週期聯動?
- Activiti中工作流的生命週期詳細解析!一個BPMN流程示例帶你認識專案中流程的生命週期
- spring bean的作用域和生命週期SpringBean
- viewController的生命週期ViewController
- Servlet的生命週期Servlet
- UIViewController的生命週期UIViewController
- Flutter 的生命週期Flutter
- Spring的生命週期Spring
- bean的生命週期Bean
- SQL的生命週期SQL
- Laravel的生命週期Laravel
- 類的生命週期
- Android Activity是如何啟動的?Activity的生命週期是如何呼叫的?Android
- Android 開發藝術探索筆記之一 -- Android 的生命週期和啟動模式Android筆記模式
- Android四大元件之服務————服務的生命週期和啟動方式Android元件
- 微服務業務生命週期流程管控引擎微服務
- thinkPHP5的應用執行流程與生命週期PHP
- React元件和生命週期簡介React元件
- Flutter路由棧和生命週期解析Flutter路由
- Flutter - 生命週期監聽和管理Flutter
- Flutter中Widget的生命週期和渲染原理Flutter
- servlet的生命週期和工作原理介紹Servlet
- vue - 生命週期Vue
- Fragment生命週期Fragment
- vue生命週期Vue