iOS应用程序(App)从开始运行到界面展示经历了哪些方法?界面是怎么显示到屏幕上的?理解了这些对我们后续的开发很有帮助。
App从启动到我们熟知的AppDelegate
代理方法经历了一下几个步骤
load方法
主要讲的是控制器的load
方法,App执行main
函数之前会优先执行load
方法。我们可以在这里交换一些控制器的生命周期及方法,经常用到的埋点基本上都是通过该方式进行处理的。
1 | + (void)load { |
main函数
main
函数在工程根目录的main.m
文件中。也是程序的唯一入口函数。
1 | int main(int argc, char * argv[]) { |
我们看下UIApplicationMain
的介绍。该函数的作用就是创建一个应用程序对象并设置代理,然后设置主事件循环。如果应用程序的Info.plist
指定了一个nib
作为程序主要入口,这个nib
将会被加载到程序中。最后还补充到,尽管声明了返回类型,但该函数永远不会返回(这也意味着主循环是一个死循环,直到程序被杀死才会停止)。
1 | UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nullable argv[_Nonnull], NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName); |
参数说明:
argc
:argv
中参数的个数,通常是main
对应的参数。argv
: 参数列表,通常也是main
对应的参数。principalClassName
:UIApplication
或子类名称- 如果设置为空,将会从
Info.plist
中查找是否指定了该值(查找key:Main storyboard file base name
)。 - 如果在
Info.plist
找到配置的值(即Main
),就会创建一个UIWindow
,把Main.storyboard
中箭头指向的控制器view添加到UIWindow
上; - 如果在
Info.plist
没有找到,将会直接使用UIApplication
,同样会创建上面描述的UIWindow
。
delegateClassName
:principalClassName
的代理类名称,初始化principalClassName
完成后代理类会被实例化(开始走AppDelegate
的代理方法)。
UIWindow
UIWindow
是一个特殊的UIView
,一般情况下一个应用程序至少有一个UIWindow
。
iOS程序启动结束后,会自动创建一个UIWindow
,然后设置跟控制器,把跟控制器的View
添加到这个UIWindow
上,最后展示在屏幕上。UIWindow
更像是一棵树的根节点,所有的子节点都从根节点或根的子节点出发。所以说如果没有UIWindow
,用户就看不到任何界面。
如果设置了Main.storyboard
启动,程序就会把Main.storyboard
中初始化箭头指向的控制器view
添加到UIWindow
上。UIWindow
的跟控制器就不需要我们自己设置了,系统会自动关联。
UIWindow
为什么要设置rootViewController
?
设置rootViewController
从程序设计角度来讲会更加合理,并且能够起到解耦的效果。从内存角度看的话,就是一个强引用指针。
手动创建UIWindow
把
Info.plist
中的Main storyboard file base name
对应的value值Main
清空,或者直接把这行移除。在
AppDelegate
加载完成的代理方法中创建UIWindow
,并显示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建UIWindow
UIWindow *window = [[UIWindow alloc] init];
window.backgroundColor = [UIColor blueColor];
// 设置UIWindow的根控制器
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor redColor];
window.rootViewController = vc;
// 让当前window成为主window并显示到屏幕上
[window makeKeyAndVisible];
// 强引用,否则会被释放
self.window = window;
return YES;
}
makeKeyAndVisible原理
UIApplicaton
属性keyWindow
指向的就是makeKeyAndVisible
的最后一次调用者。
我们通过[UIApplication sharedApplication].keyWindow
查看下创建的UIWindow
做了哪些操作。
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
通过控制台输出可以看到,keyWindow
和自己创建的window
是同一个对象,只是调用makeKeyAndVisible
之前window
的hidden
属性是YES
,调用之后hidden
属性没有了(其实就是NO
)。
总结makeKeyAndVisible
作用:
- 设置调用者
window
为当前应用程序的keyWindow
; - 显示到屏幕上;
- 把根控制器的
view
添加到自己上面。
伪代码:
1 | // UIWindow.m |
不调用makeKeyAndVisible
,设置window
的hidden
为NO
时也可以显示到屏幕,只是keyWindow
为空
伪代码:
1 | // UIWindow.m |
注意:UIWindow
的根控制器必须设置且不能给空,否则报错。iOS9
之后创建的UIWindow
不需要设置frame
了。
多个UIWindow
系统给我们提供UIWindow
的windowLevel
层级有三个常量:
1 | UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal; |
windowLevel
是CGFloat
类型,默认值是0.0
,我们可以通过他设置多个window之间的层级。
三个常量之间的层级优先级是:UIWindowLevelNormal(值:0.0)
> UIWindowLevelStatusBar(值:1000.0)
> UIWindowLevelAlert(值:2000.0)
UIWindowLevelNormal
:创建的window默认都是这个层级;UIWindowLevelStatusBar
:状态栏的层级;UIWindowLevelAlert
:键盘、系统弹框都属于这个层级。
新建window作为状态栏背景(UIWindowLevelNormal
)
不需要设置windowLevel
,或设置windowLevel
小于UIWindowLevelStatusBar
即可。
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
新建window覆盖状态栏(UIWindowLevelStatusBar
)
只需设置windowLevel
大于等于UIWindowLevelStatusBar
即可。
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
window覆盖(UIWindowLevelAlert
)
只需设置windowLevel
大于等于UIWindowLevelAlert
即可。
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
总结:如果多个window
设置了同级别,后添加的会覆盖到之前添加的上面。在实际开发中,除了首次创建的视图载体window
,建议直接使用UIWindow
的hidden
属性,少用makeKeyAndVisible
,这样可以有效避免UIApplication
属性keyWindow
的混乱。