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];
来阻塞线程达到启动页面倒计时。