iOS之UIApplication,viewController生命流程小结

UIApplication

  • 这里和大家分享一下UIApplication、ViewController和View的一些生命周期以及创建的知识,知识点比较散,但是掌握这些细节可以避免一些开发中的细节问题
  • 每个应用程序都有一个入口,而这个入口就是main函数
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • 如大家所见,程序一开始就进入main函数,然后main函数会调用UIApplicationMain这个函数,接下来讲解一下这个函数的几个参数
    • argcargv这两个参数与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的一些其他功能
iOS之UIApplication,viewController生命流程小结_第1张图片
不同协议头开启不同功能
  • Application可以管理状态栏的隐藏
[UIApplication sharedApplication]setStatusBarHidden:<#(BOOL)#> withAnimation:<#(UIStatusBarAnimation)#>
  • 但是这个方法在iOS7之后直接设置是不能直接管用的,默认情况下statusBar的隐藏由viewController自己管理
-(BOOL)prefersStatusBarHidden
  • 如果想要让Application来管理,就得到info.plist文件中去设置
info
  • 执行了UIApplicationMain方法后,就会开启事件循环,不断监听事件,事件会放入一个事件队列里
  • 当产生系统事件时,就会通知UIApplication的代理做事情,比如当程序进入后台,就会相继调用applicationWillResignActiveapplicationDidEnterBackground方法
  • 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
iOS之UIApplication,viewController生命流程小结_第2张图片
借杰哥的图一用

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设为一个控制器
iOS之UIApplication,viewController生命流程小结_第3张图片
file's owner
iOS之UIApplication,viewController生命流程小结_第4张图片
Class
  • 然后把view的outlet设给file's owner
iOS之UIApplication,viewController生命流程小结_第5张图片
连线
  • 这样就能成功的用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的界面
iOS之UIApplication,viewController生命流程小结_第6张图片
显示的是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方法底层实现了两个方法,然后颜色最终还是以外面附的值为主

  • 这次总结就到这,谢谢大家

你可能感兴趣的:(iOS之UIApplication,viewController生命流程小结)