前言
这篇文章主要介绍下有关UIApplication、UIWindow以及程序的启动流程,通过这篇文章,相信你会更加理解iOS 的应用启动过程以及app应用级别的相关知识。
UIApplication
简单介绍
UIApplication的一个主要工作是处理用户事件,它会起一个队列,把所有用户事件都放入队列,逐个处理,在处理的时候,它会发送当前事件到一个合适的处理事件的目标控件。此外,UIApplication实例还维护一个在本应用中打开的window列表(UIWindow实例),这样它就可以接触应用中的任何一个UIView对象。UIApplication实例会被赋予一个代理对象UIApplicationDelegate,以处理应用程序的生命周期事件(比如程序启动和关闭)、系统事件(比如来电、记事项警告)等等。
1.UIApplication对象是应用程序的象征,一个UIApplication对象就代表一个应用程序。
2.每一个应用都有自己的UIApplication对象,而且是单例的,如果试图在程序中新建一个UIApplication对象,那么将报错提示。
3.通过[UIApplicationsharedApplication]可以获得这个单例对象.
4. 一个iOS程序启动后创建的第一个对象就是UIApplication对象,且只有一个(通过代码获取两个UIApplication对象,打印地址可以看出地址是相同的)。
5.利用UIApplication对象,能进行一些应用级别的操作.
应用级别的操作示例
- 设置应用程序图标右上角的红色提醒数字(@property(nonatomic) NSInteger applicationIconBadgeNumber)
- 设置联网指示器的可见性(isNetworkActivityIndicatorVisible)
- iOS 中的状态栏设置(iOS9 中跟状态栏有关的已经过期,状态栏交给控制器UIViewController管理了)
- 应用界别的跳转 (openURL)
一 、设置应用程序图标右上角的红色提醒数字(如QQ消息的时候,图标上面会显示1,2,3条新信息等。)
@property(nonatomic) NSInteger applicationIconBadgeNumber;
UIApplication *app=[UIApplication sharedApplication];
app.applicationIconBadgeNumber=123;
二、设置联网指示器的可见性
@property(nonatomic,getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
代码和效果:
UIApplication *app=[UIApplication sharedApplication];
//设置指示器的联网动画
app.networkActivityIndicatorVisible=YES;
三、管理状态栏
通过UIApplication管理(一个应用程序的状态栏都由它统一管理)
如果想利用UIApplication来管理状态栏,首先得修改Info.plist的设置
View controller-based status bar appearance :NO
Status bar is initially hidden :NO
Status bar style :Opaque black style
这样在Info.plist设置后状态栏是白色的,后续可以在单个VC中通过 UIApplication 随意修改状态栏状态。
四、应用界别的跳转 (openURL)
通过这个方法可以打开本机其他应用和远程连接。
URL补充:
URL:统一资源定位符,用来唯一的表示一个资源。
URL格式:协议头://主机地址/资源路径
网络资源:http/ ftp等 表示百度上一张图片的地址 http://www.baidu.com/images/20140603/abc.png
本地资源:file:///users/apple/desktop/abc.png(主机地址省略)
UIApplication Delegate
在app受到干扰时,会产生一些系统事件,这时UIApplication会通知它的delegate对象,让delegate代理来处理这些系统事件。当应用程序启动完毕的时候就会调用(系统自动调用)。
- 应用程序的生命周期事件(启动,关闭,进入后台)
- 应用级跳转(openURL的接收)
- 注册通知、推送等
- 内存警告
########################### 值得注意的地方是 ######################
【1】在程序第一次启动的时候 不会调用applicationWillEnterForeground这个方法而
会调用 didFinishLaunchingWithOptions 这个方法。
【2】而当应用已经加载到内存后,从桌面图标点击进入应用时,就会调用applicationWillEnterForeground方法而
不会调用 didFinishLaunchingWithOptions方法
##################################################################
#当应用程序启动完毕的时候就会调用(系统自动调用)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
# 即将失去活动状态的时候调用(失去焦点, 不可交互)
- (void)applicationWillResignActive:(UIApplication *)application
# 重新获取焦点(能够和用户交互)
- (void)applicationDidBecomeActive:(UIApplication *)application
# 应用程序进入后台的时候调用
# 一般在该方法中保存应用程序的数据, 以及状态
# 双击Home键上滑杀掉APP也会调用这个方法【亲测】
- (void)applicationDidEnterBackground:(UIApplication *)application
# 应用程序即将进入前台的时候调用
#一般在该方法中恢复应用程序的数据,以及状态
- (void)applicationWillEnterForeground:(UIApplication *)application
# 应用程序即将被销毁的时候会调用该方法
#注意:如果应用程序处于挂起状态的时候无法调用该方法
- (void)applicationWillTerminate:(UIApplication *)application
#应用级跳转(openURL的接收)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
// NOTE: 9.0以后使用新API接口
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
#注册通知、推送等
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
# 应用程序接收到内存警告的时候就会调用
# 一般在该方法中释放掉不需要的内存
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
UIWindow
- UIWindow是一种特殊的UIView,通常在一个程序中只会有一个UIWindow,但可以手动创建多个UIWindow,同时加到程序里面。
- iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的View,最后将控制器的View添加到UIWindow上,于是控制器的View就显示在屏幕上了。
- 一个iOS程序之所以能显示在屏幕上,完全是因为它有UIWindow,也就是说,没有UIWindow就看不到任何UI界面。
- 状态栏和键盘都是特殊的UIWindow。
那么UIWindow是如何将View显示到屏幕上的呢?
这里有三个重要的对象UIScreen,UIWindow,UIView。
- UIScreen对象识别物理屏幕连接到设备。
- UIWindow对象提供绘画支持给屏幕。
- UIView执行绘画,当窗口要显示内容的时候,UIView绘画出他们的内容并附加到窗口上。
这样View就显示在窗口上了
用代码来加载UIWindow
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1.创建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
*************************** 类文件**********************************
//创建窗口的根控制器,并且赋值
UIViewController *rootVc = [[UIViewController alloc]init];
self.window.rootViewController = rootVc;
************************ main.storyboard ****************************
// 2.加载main.storyboard,创建main.storyboard描述的控制器
// UIStoryboard专门用来加载stroyboard
// name:storyboard名称不需要后缀
UIStoryboard *stroyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
// 加载sotryboard描述的控制器
// 加载箭头指向的控制器
UIViewController *vc = [stroyboard instantiateInitialViewController];
//根据绑定标识加载
//UIViewController *vc = [stroyboard instantiateViewControllerWithIdentifier:@"red"];
// 设置窗口的根控制器
self.window.rootViewController = vc;
***************************** xib ****************************
// 创建窗口的根控制器
// 通过xib创建控制器
ViewController *vc = [[ViewController alloc] initWithNibName:@"VC" bundle:nil];
self.window.rootViewController = vc;
// 3.显示窗口
[self.window makeKeyAndVisible];
return YES;
}
KeyWindow
什么是keyWindow,官方文档中是这样解释的"The key window is the one that is designated to receive keyboard and other non-touch related events. Only one window at a time may be the key window." 翻译过来就是说,keyWindow是指定的用来接收键盘以及非触摸类的消息,而且程序中每一个时刻只能有一个window是keyWindow。
四个关于window变化的通知:
UIWindowDidBecomeVisibleNotification
UIWindowDidBecomeHiddenNotification
UIWindowDidBecomeKeyNotification
UIWindowDidResignKeyNotification
这四个通知对象中的object都代表当前已显示(隐藏),已变成keyWindow(非keyWindow)的window对象,其中的userInfo则是空的。于是我们可以注册这个四个消息,打印信息来观察keyWindow的变化以及window的显示,隐藏的变动。
UIWindow的层级
UIWindow是有层级的,层级高的显示在最外面,当层级相同时,越靠后调用的显示在外面。
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal; //默认,值为0
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert; //值为2000
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar ; // 值为1000
所以UIWindowLevelNormal < UIWindowLevelStatusBar< UIWindowLevelAlert
并且层级是可以做加减的self.window.windowLevel = UIWindowLevelAlert+1;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.backgroundColor = [UIColor yellowColor];
[self.window makeKeyAndVisible];
UIWindow *normalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
normalWindow.backgroundColor = [UIColor blueColor];
normalWindow.windowLevel = UIWindowLevelNormal;
[normalWindow makeKeyAndVisible];
CGRect windowRect = CGRectMake(50, 50, [[UIScreen mainScreen] bounds].size.width- 100, [[UIScreen mainScreen] bounds].size.height - 100);
UIWindow *alertLevelWindow = [[UIWindow alloc] initWithFrame:windowRect];
alertLevelWindow.windowLevel = UIWindowLevelAlert;
alertLevelWindow.backgroundColor = [UIColor redColor];
[alertLevelWindow makeKeyAndVisible];
UIWindow *statusLevelWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0, 50, 320, 20)];
statusLevelWindow.windowLevel = UIWindowLevelStatusBar;
statusLevelWindow.backgroundColor = [UIColor blackColor];
[statusLevelWindow makeKeyAndVisible];
NSLog(@"Normal window level: %f", UIWindowLevelNormal);
NSLog(@"Normal window level: %f", UIWindowLevelAlert);
NSLog(@"Normal window level: %f", UIWindowLevelStatusBar);
return YES;
}
通过运行结果我们可以注意到两点:
1)我们生成的normalWindow虽然是在第一个默认的window之后调用makeKeyAndVisible,但是仍然没有显示出来。这说明当Level层级相同的时候,只有第一个设置为KeyWindow的显示出来,后面同级的再设置KeyWindow也不会显示。
2)statusLevelWindow在alertLevelWindow之后调用makeKeyAndVisible,淡仍然只是显示在alertLevelWindow的下方。这说明UIWindow在显示的时候是不管KeyWindow是谁,都是Level优先的,即Level最高的始终显示在最前面。
程序的启动过程
我们找到程序的入口main
函数,来看程序的启动过程
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
这个默认的iOS程序就是从main函数开始执行的,但是在main函数中我们其实只能看到一个方法,这个方法内部是一个消息循环(相当于一个死循环),因此运行到这个方法UIApplicationMain之后程序不会自动退出,而只有当用户手动关闭程序这个循环才结束。此时我们可以根据UIApplicationMain函数了解程序启动的过程.
- 第一个参数和第二个参数其实就是main函数的参数,分别代表:参数个数、参数内容;
- 第三个参数代表UIApplication类(或子类)字符串,这个参数默认为nil则代表默认为UIApplication类,用户可以自定义一个类继承于这个类;如果为nil则等价于NSStringFromClass([UIApplication class]).
- 第四个参数是UIApplication的代理类字符串,默认生成的是AppDelegate类,这个类主要用于监听整个应用程序生命周期的各个事件,当UIApplication运行过程中引发了某个事件之后会调用代理中对应的方法;
程序启动的完整过程
1. main函数
2.UIApplicationMain
根据第三个参数创建对应的UIApplication对象
根据第四个参数AppDelegate创建并指定此对象为UIApplication的代理.
开启主运行循环 main events loop处理事件,UIApplication会开启一个消息循环不断监听应用程序的各个活动,当应用程序生命周期发生改变UIApplication就会调用代理对应的方法。-
3.程序启动完毕的时候, 创建window,加载info.plist。
(假如有storyboard)根据Info.plist中 Main storyboard file base name 字段获得最主要storyboard的文件名,加载对应的storyboard。
系统在加载storyboard的时候会做以下三件事情
1. 创建窗口 UIWindow。
2. 加载mian.storyboard 并实例化view controller
3. 分配新视图控制器到窗口root viewcontroller,然后使窗口显在示屏幕上。
(假如没有storyboard)就不会加载storyboard,也就不会帮我们创建UIWindow,那么我们需要自己在程序启动完成的时候也就是在didFinishLaunchingWithOptions方法中创建。
1.创建窗口 UIWindow。
2. 创建并实例化view controller
3. 分配新视图控制器到窗口root viewcontroller,然后使窗口显在示屏幕上。