app启动分为冷启动,热启动
冷启动:app点击启动时,进程不在系统中
热启动:app在后台,进程还在系统中时,用户重新启动
启动步骤
一.main()函数执行前
1.加载dyld到App进程:
dyld,dynamic loader,作用是加载一个进程需要的image。
这个image不是图片的意思,它大概表示一个二进制文件(可执行文件或so文件),里面是被编译过的符号、代码等,所以imageLoader作用是将这些文件加载进内存,且每一个文件对应一个imageLoader实例来负责加载。
2.加载动态库:
dyld首先读取mach-o文件的header和load commands load commands里有可执行文件依赖的动态库,递归加载,缓存到dyld shared cache 可以通过命令行otool查看:
192:Desktop Leo$ otool -L demo
demo:
@rpath/PullToRefreshKit.framework/PullToRefreshKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1444.12.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
@rpath/libswiftCore.dylib (compatibility version 1.0.0, current version 900.0.65)
@rpath/libswiftCoreAudio.dylib (compatibility version 1.0.0, current version 900.0.65)
//...
3.Rebase和Bind:
为什么要rebase?
有两种技术保障应用的安全:ASLR和Code Sign ASLR:App被启动的时候,让起始地址随机,黑客不容易通过起始地址和偏移量找到函数地址
Code Sign:签名,hash算法获得摘要,用私钥加密,获得签名
mach-o文件中有很多符号,有指向当前mach-o的,也有指向其他dylib的, 运行的时候,dyld通过rebase修正指向当前mach-o的函数或变量的指针指向 bind修正指向外部的指针指向
4.初始化Objective C runtime:
OC是动态语言,所以执行main之前,需把类注册到全局table中 selector 唯一性检查 OC支持Category,初始化的时候也会把分类中的方法注册到对应的类中
5. Initializers:
+load方法 C/C++静态初始化对象和标记为__attribute__(constructor)的方法 使用__attribute__(constructor)可以保证该方法在main函数之前被调用 attribute((constructor)) static void beforeFunction() { printf("beforeFunction\n"); }
知识点:
Mach-O文件 Mach Object文件格式,用于可执行文件,目标代码,动态库的文件格式
header:可执行的CPU架构,x86,arm64 Load Commands :加载命令,包含文件组织架构和在虚拟内存中的布局 Data:数据,包含Load Commands 中需要的各个段的数据 可以使用MACHOView查看内容
dyld: dyld的全称是dynamic loader,它的作用是加载一个进程所需要的image,dyld是开源的。
Page fault: 在应用执行的时候,它被分配的逻辑地址空间都是可以访问的,当应用访问一个逻辑Page,而在对应的物理内存中并不存在的时候,这时候就发生了一次Page fault。当Page fault发生的时候,会中断当前的程序,在物理内存中寻找一个可用的Page,然后从磁盘中读取数据到物理内存,接着继续执行当前程序。 dyld2和dyld3的区别:
dyld2是纯粹的in-process,也就是在程序进程内执行的,也就意味着只有当应用程序被启动的时候,dyld2才能开始执行任务。 dyld3则是部分out-of-process,部分in-process。图中,虚线之上的部分是out-of-process的,在App下载安装和版本更新的时候会去执行
out-of-process会做如下事情:
分析Mach-o Headers
分析依赖的动态库
查找需要Rebase & Bind之类的符号
把上述结果写入缓存
二.main函数:
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([SXAppDelegate class])); } }
UIApplicationMain函数做了什么事:
1.根据第三个参数创建UIApplication对象,nil时,即为@“UIApplication”
2.根据传入的代理名创建代理对象,这里为SXAppDelegate
3.main函数为什么不退出?
开启RunLoop,不开启的话,main执行完程序就结束了
注册两个Observer,所以不退出
第一个Observer监视事件kCFRunLoopEntry,回调为_wrapRunLoopWithAutoreleasePoolHandler,
回调内部调用_objc_autoreleasePoolPush()创建自动释放池,
第二个Observer监视两个事件:kCFRunLoopBeforeWaiting,kCFRunLoopExit kCFRunLoopBeforeWaiting时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧池,创建新池,kCFRunLoopExit时调用_objc_autoreleasePoolPop()
4.解析info.plist文件
5.调用application:didFinishLaunchingWithOptions:
首屏渲染: info.plist文件中 如果 有值,加载SXMain.storyboard
创建一个UIWindow实例显示界面
设置窗口根控制器
根据storyboard设置,创建控制器,设置此为window的根控制器
设置self.window可见并设置UIApplication的keyWindow
将根控制器的view添加到window上
没有值,需要代码初始化
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //创建window对象
UIViewController *viewController = [[UIViewController alloc] init];
self.window.rootViewController = viewController; //设置根控制器
[self.window makeKeyAndVisible]; //将window设置为UIApplication的keyWindow,让window可见
三.首屏渲染完成后
其他业务模块初始化,监听注册,配置读取
阶段为首屏渲染完成到didFinishLaunchingWithOptions方法结束
attribute(blog.sunnyxx.com/2016/05/14/…) (www.jianshu.com/p/0237c3415…