iOS应用程序的启动过程

关键步骤

一个程序从main函数开始启动。代码如下:

int main(int argc, char * argv[]) {

@autoreleasepool {

return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}

}

可以看到main函数会调用UIApplicationMain函数,它的四个参数的意思是:

1、argc: 代表程序在进入main函数时的参数的个数。默认为1。

2、argv: 代表包含的各个参数。默认为程序的名字。

3、principalClassName: UIApplication或者它的子类的名字, 如果传入的是nil, 则表示UIApplication的名字, 即@"UIApplication"。

4、delegateClassName: UIApplication的代理的名字。

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

1、根据传入的名称创建UIApplication对象。

2、根据传入的代理名称创建UIApplication代理对象。

3、开启事件循环(如果不进行循环,那么在main函数结束后程序就结束了。要保证程序创建后可以一直存在)。

4、解析Info.plist文件:

会在Info.plist文件里查找Main storyboard file base name这个Key对应的Value是否有值。如果有值,则表示之后会通过Storyboard加载控制器,AppDelegate会接收到didFinishLaunchingWithOptions消息(程序启动完成的时候),此时Storyboard会进行一系列的加载操作(后面会具体说);如果没有值,则不会通过Storyboard加载控制器,接着AppDelegate会接收到didFinishLaunchingWithOptions消息(程序启动完成的时候),在这个时候需要我们通过代码的方式加载控制器。

注意Info.plist中Main storyboard file base name这个Key并不是真正的Key,而是苹果为了增强可读性才这样写的,真正的Key为UIMainStoryboardFile(可以通过Info.plist文件的源代码查看)。

这就是在想要用代码方式创建控制器而不是Storyboard创建控制器的时候为什么先要将Main Interface设置为空白,这样在解析Info.plist文件的时候才会知道不通过Storyboard创建控制器。

由此可以知道,解析Info.plist文件这一操作主要是看我们用的是Storyboard方式加载还是代码的方式加载。默认Main storyboard file base name为Main,也就是通过Storyboard方式加载控制器。

现在具体分析一下,通过Storyboard方式加载控制器和代码方式加载控制器。

通过Storyboard

通过Storyboard,主要做了下面的事情(这些事情不需要我们做,是系统自动完成的,在程序启动完成的时候):

创建窗口。

创建一个UIWindow的实例用来显示界面。

设置窗口的根控制器。

根据Storyboard的设置,创建一个控制器。

并且设置这个控制器为之前创建的window的根控制器。

显示窗口。(相当于后面提到的makeKeyAndVisible)

设置self.window可见并且设置UIApplication的keyWindow。

在这一步中将根控制器的view添加到window上。

通过代码方式

通过代码的方式,需要我们在didFinishLaunchingWithOptions方法中进行加载控制器的相关操作。代码如下:

- (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是系统自带的属性)

接着设置窗口的根控制器。

不再根据Storyboard中的设置加载,此时需要我们自己创建控制器。

设置这个控制器为self.window的根控制器。

注意这个时候根控制器的view还没有加到self.window上,当窗口要显示的时候,才会把窗口的根控制器的view添加到窗口。(可以输出self.window.subViews来验证)

显示窗口。代码如下:

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

首先,将self.window设置为UIApplication的keyWindow,这么做是方便我们以后查看UIApplication的主窗口是哪一个。

接着,让self.window可见,相当于执行的代码是:

self.window.hidden = NO;

这么做的原因是self.window默认hidden = YES,所以需要让其显示出来。

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

经过这一步,界面将要显示,此时根控制器的view会加到self.window上以正常显示。

这里有一点要注意:

系统创建的AppDelegate自带一个属性位于.h文件中:

@property (strong, nonatomic) UIWindow *window;

当用Storyboard的方式加载控制器,在应用启动完成的时候(didFinishLaunchingWithOptions)需要一个UIWindow的实例来显示界面,所以Apple提供了这个window属性。系统根据storyboard自动创建一个window,然后将window赋值给这个window属性,以保证完成之后的工作。

当用代码的方式加载控制器,同样的,首先也需要一个UIWindow的实例来显示界面,因为不使用Storyboard所以这次要我们自己创建window。此时有两种做法,第一种是在didFinishLaunchingWithOptions方法中创建一个UIWindow对象:

UIWindow *myWindow = [[UIWindow alloc] initWithFrame:...];

但是如果用这种方法运行程序会发现界面依然无法显示出来,因为此时myWindow是一个局部变量,当didFinishLaunchingWithOptions方法执行完毕这个变量就会销毁。所以更好的办法是直接使用系统提供的window属性:

self.window = [[UIWindow alloc] initWithFrame:...];

之前的例子也是这么做的。

另外,仔细观察会发现这个window属性的修饰符是strong,而不是weak。想想之前使用weak来修饰一个控件是因为这个控件会被加到一个view中,这个view的subViews数组会有强引用指向控件,所以用weak是没有问题的。现在这种情况,因为window控件不会被加到其他view中,即没有其他的强指针指向这个对象,所以在创建的时候需要将修饰符设置成strong以保证创建出的window不会被销毁。(Apple创建的window属性的修饰符是strong)

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以及状态栏就可以按层级关系正常显示。

概览

这里PY为前缀名:

1.先执行main函数,main内部会调用UIApplicationMain函数

2.UIApplicationMain函数里面做了什么事情:

(1)创建UIApplication对象

(2)创建UIApplication的delegate对象—–PYAppDelegate

(3)开启一个消息循环:每监听到对应的系统事件时,就会通知MJAppDelegate

(4)为应用程序创建一个UIWindow对象(继承自UIView),设置为PYAppDelegate的window属性

(5)加载Info.plist文件,读取最主要storyboard文件的名称

(6)加载最主要的storyboard文件,创建白色箭头所指的控制器对象

(7)并且设置第6步创建的控制器为UIWindow的rootViewController属性(根控制器)

(8)展示UIWindow,展示之前会将添加rootViewController的view到UIWindow上面(在这一步才会创建控制器的view)

[window addSubview: window.rootViewControler.view];

进入main函数,在main.m的main函数中执行了UIApplicationMain这个方法,这是ios程序的入口点!

int UIApplicationMain(int argc, char argv[], NSString principalClassName, NSString *delegateClassName)

argc、argv:ISO C标准main函数的参数,直接传递给UIApplicationMain进行相关处理即可

principalClassName:指定应用程序类,该类必须是UIApplication(或子类)。如果为nil,则用UIApplication类作为默认值

delegateClassName:指定应用程序类的代理类,该类必须遵守UIApplicationDelegate协议

此函数会根据principalClassName创建UIApplication对象,根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性

lUIApplication对象会依次给delegate对象发送不同的消息,接着会建立应用程序的main runloop(事件循环),进行事件的处理(首先会调用delegate对象的 application:didFinishLaunchingWithOptions:)

程序正常退出时这个函数才返回。如果进程要被系统强制杀死,一般这个函数还没来得及返回进程就终止了。


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

推荐阅读更多精彩内容