欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

详解iOS应用程序的启动过程

程序员文章站 2023-12-03 14:20:28
关键步骤 一个程序从main函数开始启动。 复制代码 代码如下: int main(int argc, char * argv[]) {   &...

关键步骤
一个程序从main函数开始启动。

复制代码 代码如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return uiapplicationmain(argc, argv, nil, nsstringfromclass([appdelegate class]));
    }
}

可以看到main函数会调用uiapplicationmain函数,它的四个参数的意思是:
  • argc: 代表程序在进入main函数时的参数的个数。默认为1。
  • argv: 代表包含的各个参数。默认为程序的名字。
  • principalclassname: uiapplication或者它的子类的名字, 如果传入的是nil, 则表示uiapplication的名字, 即@"uiapplication"。
  • delegateclassname: uiapplication的代理的名字。

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

  • 根据传入的名称创建uiapplication对象。
  • 根据传入的代理名称创建uiapplication代理对象。
  • 开启事件循环(如果不进行循环,那么在main函数结束后程序就结束了。要保证程序创建后可以一直存在)。
  • 解析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:)

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

下面我们有图有真相吧!!!