iOS-APP的启动流程和生命周期

知 识 点 / 超 人


更新2019-03-27:添加了+load与+initialize方法的补充说明


前言
当自己对技术对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就是在这个时候被初始化并完成OC的内存布局)
    • 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方法中被发生。

dyld流程

对于动态库和静态库的链接方式是不同的,详细的大家可以看看我另外一篇关于动态库与静态库的文章

从整个APP启动流程中,我们可以做优化的点主要有
1.是减少系统依赖库
2.减少自己需要加入的各种三方库(库越少dyld加载的速度越快,就能越早的返回程序入口main函数的地址)
3.有一些自己加入的库,能选择静态库就选择静态库,少用动态库,因为动态库的加载方式比静态库慢。如果必须依赖动态库,则把多个非系统的动态库合并成一个动态库。
4.自己加入的各种framework库根据情况设为optional和required,如果该framework在当前App支持的所有iOS系统中都存在(模拟器,真机),那么就设为required,否则就设为optional。(Required(强引用)的framework一定会被加载到内存中,及时不使用也会被加载到内存中。Optional(弱引用)的framework启动的时候并不会加载,在使用的时候才会进行加载,可以减少启动时加载动态库的时间。)
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时控制台会输出的启动耗时内容。

DYLD_PRINT_STATISTICS设为 1


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)
    1. 程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions:方法
      在application:didFinishLaunchingWithOptions:中创建UIWindow
      创建和设置UIWindow的rootViewController
    1. 最终显示第一个窗口

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];

来阻塞线程达到启动页面倒计时。

  • 3.+load的调用时机与规则
    当类被程序引用的时候就会调用类的+load方法,当程序启动时,会加载相关Mach-O文件,这个时候就会查找项目中那些文件被引用,这个时候就会调用+load。
    1.当父类和子类都实现+load方法时,父类+load方法的执行顺序要优先于子类执行
    2.当子类未实现+load方法时,会调用父类+load方法
    3.类中的+load方法执行顺序要优先于类别(Category)中的+load方法,虽然在APP启动流程中,Category的加载顺序在OC的+load方法之前,但是Category中的+load方法的执行顺序却在OC类的+load方法之后。
    4.当有多个类别(Category)都实现了+load方法时,这几个+load方法都会执行,其执行顺序与类别文件在Build Phases里的Compile Sources中出现顺序一样。
    5.由4可以得知,当有多个不同的类或者不同类的Category的时候,每个类+load 执行顺序与其在Compile Sources出现的顺序一致。
    6.+load内部是加了线程锁的,因此如果内部执行内容过多,会造成线程阻塞。

  • 4.+initialize的调用实际与规则
    不同于+load,类中的+initialize是在引用类被首次使用时被调用。也就是说也许你工程里import了一个类,但是你并没有调用这个类的任何方法,那么这个类的+initialize方法就不会被调用。而+initialize方法也只会调用一次,那就是首次调用这个类的任意方法时。
    1.父类的+initialize方法会比子类的先执行
    2.当子类未实现+initialize方法时,会调用父类+initialize方法,子类实现+initialize方法时,想要父类也调用,需要在子类实现里 [super initialize].
    3.当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法,因为覆盖的原因,所以最后一个覆盖了前面的)
    4.+load内部是加了线程锁的,因此如果内部执行内容过多,会造成线程阻塞。


参考文献

iOS启动时间优化
iOS程序启动->dyld加载->runtime初始化(初识)
dyld 加载 Mach-O

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容