UIApplication
- 这里和大家分享一下UIApplication、ViewController和View的一些生命周期以及创建的知识,知识点比较散,但是掌握这些细节可以避免一些开发中的细节问题
- 每个应用程序都有一个入口,而这个入口就是
main
函数
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 如大家所见,程序一开始就进入
main
函数,然后main
函数会调用UIApplicationMain
这个函数,接下来讲解一下这个函数的几个参数-
argc
,argv
这两个参数与main函数的参数一致 - 第三个参数应该传入
UIApplication
类或者其子类 - 第四个参数就是传入
UIApplication
的代理类 - 第三第四个参数都要求传入字符串,但最好还是返回用
反射
方法返回其类名 - 第三个参数传入空的话,会默认传入
UIApplication
类
-
- UIApplication知识小贴士
- 每一个应用都有自己的UIApplication对象,而且是单例
- UIApplication对象是应用程序的象征
- 通过[UIApplication sharedApplication]可以获得这个单例对象
- 一个iOS程序启动后创建的第一个对象就是UIApplication对象
- 利用UIApplication对象,能进行一些应用级别的操作
/*************************/
- UIApplication的一些属性
//应用角标提醒属性
@property(nonatomic) NSInteger applicationIconBadgeNumber; //在iOS8之后,要实现-[UIApplication registerUserNotificationSettings:]方法才能够成功设置
//网络使用情况提醒
@property(nonatomic,getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
//状态栏的类型
@property(nonatomic) UIStatusBarStyle statusBarStyle;
//状态栏的方向
@property(nonatomic) UIInterfaceOrientation statusBarOrientation;
- Application的一些其他功能
- Application可以管理状态栏的隐藏
[UIApplication sharedApplication]setStatusBarHidden:<#(BOOL)#> withAnimation:<#(UIStatusBarAnimation)#>
- 但是这个方法在iOS7之后直接设置是不能直接管用的,默认情况下statusBar的隐藏由viewController自己管理
-(BOOL)prefersStatusBarHidden
- 如果想要让Application来管理,就得到info.plist文件中去设置
- 执行了
UIApplicationMain
方法后,就会开启事件循环,不断监听事件,事件会放入一个事件队列里 - 当产生系统事件时,就会通知
UIApplication
的代理做事情,比如当程序进入后台,就会相继调用applicationWillResignActive
与applicationDidEnterBackground
方法 -
UIApplicationMain
是一个死循环函数,当程序正常退出时才会返回
UIApplication的几个代理事件解析
- 方法比较多,就直接合着代码解释
/**
* 会先加载info.plist文件,和启动图片,default.png
* 程序加载完毕的时候调用
*
*/
//这里再详细说明一下,程序进来后,会先去查看info.plist文件,加载启动图片,顺便查看是否设置了Main Interface,也就是否指定了加载的xib(storyboard)文件,如果有,则启用xib,后面会说明xib文件的启用过程
//当配置完info.plist文件里所需要的配置后,那么就来到这个方法里
//我们可以在这个方法里自己创建Window,然后设置想要的根控制器
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
//这个方法的调用在applicationDidEnterBackground之前,它代表着应用将不接受用户的事件,我们称之为失去焦点
- (void)applicationWillResignActive:(UIApplication *)application
//当程序失去焦点之后就会来到这个方法,程序已经完全进入了后台
- (void)applicationDidEnterBackground:(UIApplication *)application
//当用户再次进入程序,而程序还没有被杀死时,就会先调用这个方法
- (void)applicationWillEnterForeground:(UIApplication *)application
//接着当应用完全显示在用户面前时,用户可以开始与应用交互时就调用下面的方法
- (void)applicationDidBecomeActive:(UIApplication *)application
UIWindow解析
-
UIWindow
是一种特殊的UIView
,通常在一个app中只会使用一个UIWindow
- statusBar与keyboard(键盘)也分别属于自己的window
- 苹果是这样说的,UIScreen用来connect设备硬件,而UIWindow才是为了显示
- 你可以发现,UIWindow也可以像view一样,设置背景颜色,可以摆放控件,但是,UIWindow不能旋转时自动调整控件的位置
- UIWindow需要一个根控制器,而UIApplication需要一个主窗口- UIWindow是有层级性的
/*窗口的层级
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;0
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;2000
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar;1000
*/
//手动创建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
/*
创建控制器
*/
[self.window makeKeyAndVisible];
//上面这句代码会在底层做这些事
// 1.让窗口成为应用程序的主窗口 2.设置窗口不要给隐藏
// 1.1 application.keyWindow = self.window
// 2.1 self.window.hidden = NO;
- 窗口的层级是可以设置的,如果不设置,后创建的window会在先创建的window前
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor yellowColor];
[self.window makeKeyAndVisible];
self.window1 = [[UIWindow alloc] initWithFrame:CGRectMake(50, 400, 300, 300)];
self.window1.backgroundColor = [UIColor redColor];
self.window1.hidden = NO;
- 还有一点细节
//如果设定了程序启动的storyboard,那么程序会自动作以下几件事情
// 1.创建窗口
// 2.加载Main.storyboard,并且创建storyboard描述的控制器
// 3.把storyboard描述的控制器作为窗口的根控制器
// 4.显示窗口
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"%@",application.keyWindow);//这时候打印,window已经存在
return YES;
}
几种手动加载控制器的方法
storyboard
- 让我们看看,自动加载storyboard在底层要做哪些步骤
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 1.创建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 2.加载storyboard文件,创建控制器
// name:就是storyboard文件名
// bundle:主bundle,传入nil,表示主bundle
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
// 通过storyboard对象创建控制器
// UIViewController *rootVc = [storyboard instantiateViewControllerWithIdentifier:@"vc"];
// instantiateInitialViewController:加载storyboard箭头指向的控制器
UIViewController *rootVc = [storyboard instantiateInitialViewController];
// 3.设置窗口的根控制器,并且显示窗口
self.window.rootViewController = rootVc;
// 4.显示窗口
[self.window makeKeyAndVisible];
return YES;
}
xib
- xib手动创建控制器需要给它的view设置管理的控制器
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1.创建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 2.设置窗口的根控制器
UIViewController *rootVc = [[UIViewController alloc] initWithNibName:@"VC" bundle:nil];
self.window.rootViewController = rootVc;
// 3.显示窗口
[self.window makeKeyAndVisible];
// Override point for customization after application launch.
return YES;
}
- 如果光是像上面这样做,是不够的,运行起来会崩溃
- 报错:
reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "VC" nib but the view outlet was not set.'
- 还需要接下来几个步骤,来设置View的outlet控制器
- 把xib的file's owner的class设为一个控制器
- 然后把view的outlet设给file's owner
- 这样就能成功的用xib去创建控制器了
- xib加载控制器还有一点细节是要注意的
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 假设通过Xib创建HJSViewController控制器对象
// 1.如果nibName为nil,首先会去查找HJSView.xib,如果有,就会去加载HJSView.xib描述的控制器的view
// 2.如果没有找到,就会去找HJSViewController.xib,如果有,也会去加载
// 3.都没有找到,就会生成一个空的view
// init底层就会调用initWithNibName:bundle:
HJSViewController *vc = [[HJSViewController alloc]init];
// HJSViewController *vc = [[HJSViewController alloc] initWithNibName:nil bundle:nil];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
代码手动创建
- 这个就没什么特别的了,正常的三部曲
- 创建窗口
- 创建控制器
- 把控制器设置为窗口的根控制器
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
ViewController *vc = [[ViewController alloc] init];
vc.view.backgroundColor = [UIColor redColor];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
View的创建以及加载
- 先来说说
loadView
这个方法 - 这个方法我们一般不会去重写它,而是默认调用父类的
- 一旦重写它,标明你想给控制器的
self.view
创建一个自己想要的View,比如只显示图片的imageView
// 注意:一旦重写了loadView,就不要调用[super loadView]
- (void)loadView
{
// 调用父类(UIViewController)
// 调用系统默认的做法
[super loadView];
}
如果在里面什么都不写,那么这个控制器就一片漆黑了
只要没有重写loadView,系统就会自动判断有没有storyboard或者xib描述控制器的View,如果有就会去加载它们描述控制器的view
loadView
的调用是懒加载的,需要用到View的时候才会加载,这个稍后会分析前面说过,在ViewController的init方法里,底层会去想办法寻找可以创建View的xib,如果最终找不到,就会创建一个空的View,viewController的View在设为跟控制器的时候,因为需要显示,所以会默认的去创建这个空的view,而这个空的View也有一些好玩的细节
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor redColor];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn.center = CGPointMake(50, 50);
[self.window addSubview:btn]
ViewController *vc = [[ViewController alloc] init];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
- 你会发现,你看到的是window的界面
当你点击
button
的时候,你会发现无法点击,说明事件被ViewController的View拦截下来了实际上这个空的View的背景色被设置为了
Clear color
但是透明度却不完全为0
透明度拦截事件点击有一个临界值,也就是0.02
大于0.02事件就会被拦截,反之则会穿透
最后来说一下ViewController的View的getter方法,这个方法会在底层调用一些其他的方法
- (UIView *)view
{
if (_view == nil) {
[self loadView];
[self viewDidLoad];
}
return _view;
}
- 不明白这个原理的朋友或许会产生一些莫名其妙的问题
- 大家来看下面一个例子
- 我在UIApplication代理类里的didFinishLaunchingWithOptions方法里这样写
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 创建控制器的View,在创建控制器的时候,并不会加载控制器的view
ViewController *vc = [[ViewController alloc] init];
// UIViewController控制器的View第一次使用的时候加载
vc.view.backgroundColor = [UIColor redColor];
self.window.rootViewController = vc;
// 显示窗口,窗口上的东西也需要显示
[self.window makeKeyAndVisible];
- 然后再在ViewDidLoad方法里改背景色
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor yellowColor];
}
最终你会看到的背景色是红色的
就是因为view的getter方法底层实现了两个方法,然后颜色最终还是以外面附的值为主
这次总结就到这,谢谢大家