笔记-iOS应用程序的启动过程

1,666 阅读4分钟

程序的启动

  1. 使用Xcode打开一个项目,很容易会发现一个文件main.m文件,此处就是应用的入口。
  2. 程序启动时,先执行main函数,main函数是iOS程序的入口点
  3. 内部会调用UIApplicationMain函数
  4. UIApplicationMain里会创建一个UIApplication对象
  5. 然后创建UIAPPlication的delegate对象
  6. 然后AppDelegate,开启一个消息循环(main runloop)每当监听到对应的系统事件时,就会通知AppDelegate
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

UIApplication对象是应用程序的象征,每一个应用都有自己的UIApplication对象,而且是单例的。通过[UIApplication sharedApplication]可以获得这个单例对象,一个iOS程序启动后创建的第一个对象就是UIApplication对象,利用UIApplication对象,能进行一些应用级别的操作。

UIApplicationMain函数实现如下

int UIApplicationMain {
    int argc,
    char *argv[],
    NSString *principalClassName,
    NSString *delegateClassName
}

第一个参数表示程序在进入mian函数是的参数的个数,默认为1
第二个参数表示装载函数的数组(包含的各个参数),默认为程序的名字
第三个参数是UIApplication类名或其子类名,若是nil,则默认使用UIApplication类名。
第四个参数是协议UIApplicationDelegate的实例化对象名,这个对象就是UIApplication对象监听到系统变化的时候通知其执行的相应方法。

在UIApplicationMain函数中,根据传入的UIApplication名称和它的代理的名称,会主要做下面的事情:

  • 根据传入的名称创建UIApplication对象
  • 根据传入的代理名称创建UIApplication代理对象
  • 开启事件循环(如果不进行循环,那么在main函数结束后程序就结束了。要保证程序创建后可以一直存在)
  • 解析Info.plist文件:
    会在Info.plist文件里查找Main storyboard file base name这个Key对应的Value是否有值。如果有值,则表示之后回通过Storyboard加载控制器,APPDelegate会接收到didFinishLaunchingWithOptions消息(程序启动完成的时候),此时storyboard会进行一系列的加载操作;如果没有值,则不会通过storyboard加载控制器,接着AppDelegate会接收到didFinishLaunchingWithOptions消息(程序启动完成的时候),这个时候需要我们通过代码的方式加载控制器。

启动完毕会调用didFinishLaunching方法,并在这个方法中创建UIWindow,设置AppDelegate的window属性,并设置UIWindow的根控制器

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    UIViewController *viewController = [[UIViewController alloc] init];
    self.window.rootViewController = viewController;
    // 此时根控制器的view还没有加到self.window上
    [self.window makeKeyAndVisible];
    // 此时根控制器的view加到self.window上
    return YES;
}

首先创建窗口,得到一个正确的UIWindow实例对象用来显示界面(self.window是系统自带的属性)。接着设置窗口的根控制器。自己创建控制器,设置这个控制器为self.window的根控制器。注意这个时候根控制器的view还没有加到self.window上,当窗口要显示的时候,才会把窗口的根控制器的view添加到窗口。
显示窗口:

[self.window makeKeyAndVisible] // 实际做了下面的事

首先将self.window设置为UIApplication的keyWindow,这么做是方便我们以后查看UIApplication的主窗口是哪一个。
接着,让self.window可见,相当于执行的代码是:

self.window.hidden = NO;

那么既然makeKeyAndVisible执行的是以上操作,实际上将[self.window makeKeyAndVisible]替换为self.window.hidden = NO,那么界面也会正常显示出来,因为makeKeyAndVisible内部就是这么做的。但是此时并没有设置UIApplication的keyWindow,为了以后方便访问,还是用makeKeyAndVisible更好一点。

UIWindow的补充

window是有层级的,并且可以有多个window同时存在。比如:状态栏就是一个window,键盘也是一个window。 可以通过设置UIWindow的对象的windowLevel属性来调整层级。
self.window.windowLevel = UIWindowLevelStatusBar;
window共有三种等级:UIWindowLevelNormal, UIWindowLevelStatusBar, UIWindowLevelAlert。如果三种等级同事出现在屏幕上,那么alert在最上面,statusBar在中间,normal则在最下面。
注意:如果一个程序中有多个window,控制器默认会把状态栏隐藏。
解决办法:关闭控制器对状态栏的控制,(为Info.plist增加View controller-based status bar appearance这个key并设置为NO),这样这些window以及状态栏就可以按层级关系正常显示。

概览

  1. 先执行main函数,main内部会调用UIApplicationMain函数
  2. UIApplicationMain函数里面做了什么事情
    • 创建UIApplication对象
    • 创建UIApplication的delegate对象——AppDelegate
    • 开启一个消息循环:每监听到对应的系统事件时,就会通知AppDelegate
    • 为应用程序创建一个UIWindow对象(继承自UIView),设置为AppDelegate的window属性
    • 加载Info.plist文件,读取最主要storyboard文件的名称
    • 加载最主要的storyboard文件,创建白色箭头所指的控制器对象
    • 并且设置上一步创建的控制器为UIWindow的rootViewController属性(根控制器)
    • 展示UIWindow,展示之前会将添加rootViewController的view到UIWindow上面(在这一步才回创建控制器的view)
[window addSubview:window.rootViewController.view];