2.2 视图控制器——UIViewController

UIViewController是UIKit框架中Controller部分的基础,一些复杂的Controller类也是基于UIViewController继承出来的。在开发应用程序时,所有界面也都是基于UIViewController搭建出来的。

2.2.1 UIViewController的生命周期

生命周期是指一个对象从创建出来到其被释放销毁的整个过程。Objective-C和Swift都是面向对象的高级语言,为了保持内存的平衡与程序运行的高效,当需要一个对象时,它会被创建并给它分配内存空间;同样,当它不再被需要时,也应该被系统释放回收。在一个UIViewController对象从创建到释放过程中,会依次调用许多生命周期函数,了解这些函数的调用时机和功能是iOS 开发者的必修课。打开Xcode 开发工具,创建一个名为UIViewControllerTest的工程,将使用的开发语言选择为Objective-C。工程创建出来后,系统的模板自动生成一个ViewController类,这个类是继承于UIViewController并且与Main.storyboard中的初始视图控制器关联。简单来说,这个类创建了一个视图控制器作为工程的根视图控制器,我们可以在这个类中编写相关代码来对UIViewController的生命周期进行研究。

UIViewController中与生命周期相关的函数有很多,列举如下:

        //类的初始化方法
        + (void)initialize;
        //对象初始化方法
        - (instancetype)init;
        //从归档初始化
        - (instancetype)initWithCoder:(NSCoder *)coder;
        //从nib文件初始化
        -(void)awakeFromNib;
        //加载视图
        -(void)loadView;
        //将要加载视图
        - (void)viewDidLoad;
        //将要布局子视图
        -(void)viewWillLayoutSubviews;
        //已经布局子视图
        -(void)viewDidLayoutSubviews;
        //内存警告
        - (void)didReceiveMemoryWarning;
        //已经展示
        -(void)viewDidAppear:(BOOL)animated;
        //将要展示
        -(void)viewWillAppear:(BOOL)animated;
        //将要消失
        -(void)viewWillDisappear:(BOOL)animated;
        //已经消失
        -(void)viewDidDisappear:(BOOL)animated;
        - (void)didReceiveMemoryWarning;
        //被释放
        -(void)dealloc;

实践是最好的老师,在编程的学习中亦是如此,要了解上面方法的执行次序,跟踪程序的运行是最快的方法,先在ViewController.m文件中将这些方法实现,代码如下:

        #import "ViewController.h"
        int tip=0;
        @interface ViewController ()
       @end
       @implementation ViewController
        +(void)initialize{
            [super initialize];
            NSLog(@"%d initialize", ++tip);
        }
        - (instancetype)init
       {
       self = [super init];
       if (self) {
 

       }
       NSLog(@"%d init", ++tip);
       return self;
       }
       - (instancetype)initWithCoder:(NSCoder *)coder
       {
       self = [super initWithCoder:coder];
       if (self) {
 

       }
       NSLog(@"%d initWithCoder", ++tip);
       return self;
       }
       -(void)awakeFromNib{
       [super awakeFromNib];
       NSLog(@"%d awakeFromNib", ++tip);
       }
       -(void)loadView{
       [super loadView];
       NSLog(@"%d loadView", ++tip);
       }
       - (void)viewDidLoad {
       [super viewDidLoad];
       NSLog(@"%d viewDidLoad", ++tip);
       }
       -(void)viewWillLayoutSubviews{
       [super viewWillLayoutSubviews];
       NSLog(@"%d viewWillLayoutSubviews", ++tip);
       }
       -(void)viewDidLayoutSubviews{
       [super viewDidLayoutSubviews];
       NSLog(@"%d viewDidLayoutSubviews", ++tip);
       }
       - (void)didReceiveMemoryWarning {
       [super didReceiveMemoryWarning];
       NSLog(@"%d didReceiveMemoryWarning", ++tip);
       }
       -(void)viewWillAppear:(BOOL)animated{
       [super viewWillAppear:animated];
       NSLog(@"%d viewWillAppear", ++tip);
       }
       -(void)viewDidAppear:(BOOL)animated{
       [super viewDidAppear:animated];
       NSLog(@"%d viewDidAppear", ++tip);
       }
       -(void)viewWillDisappear:(BOOL)animated{
       [super viewWillDisappear:animated];
       NSLog(@"%d viewWillDisAppear", ++tip);
       }
       -(void)viewDidDisappear:(BOOL)animated{
       [super viewDidDisappear:animated];
       NSLog(@"%d viewDidDisappear", ++tip);
       }
       -(void)dealloc{
       NSLog(@"%d dealloc", ++tip);
       }
       @end

上面代码中,创建一个全局变量tip,使用这个变量来对程序的运行过程进行标记。ViewController是UIViewController的一个子类,在子类中覆写父类的方法时,需要先调用父类的此方法。关于self和super这两个关键字一直是初学者的噩梦,甚至在许多时候,拥有一定开发经验的开发者也不能完全理解其意义。在这里读者只需要记住,在实例方法中(又称减方法,以减号开头), self 指调用这个方法的对象,即当前类的一个实例对象,如果用super调用方法则代表从父类中找这个方法实现,而在类方法中(又称加方法,以加号开头), self指当前类,如果用super调用方法则代码从父类中找当前类方法的实现。

提示

dealloc方法是唯一一个不需要并且也不能在实现里调用父类方法的函数,这个函数在ARC(自动引用计数)环境中不再被开发者所需要,但是开发者依然可以重写这个函数来监测内存的释放情况。

运行工程,在Xcode的调试区会打印出如图2-2的信息。

图2-2 跟踪程序运行的打印信息

从打印信息中可以清晰地看到一个UIController被创建的过程中依次会调用的方法,但是这些信息并不全面,并没有体现出UIViewController销毁时的过程,也没有展现从不同来源初始化的UIVewController生命周期的不同。在列出的生命周期方法中,initialize方法比较特殊,这个函数并不会在每次创建对象时都调用,只在这个类第一次创建对象的时候会调用做一些类的准备工作,实际上,如果有继承的子类,如果子类没有实现这个initialize方法,当第一次创建子类对象时父类会代替子类再调用一次initialize方法。

init和initWithCoder方法作用相似,都是对对象做初始化工作,如果从代码进行初始化则会调用init方法,从归档文件进行初始化则会调用initWihCoder方法。awakeFromNib方法会在从xib或者storyboard中加载的UIViewController将要激活时被调用。

loadView 方法是开始加载UI 视图的初始方法,这个方法除非开发者手动调用,否则在UIViewController的生命周期中只会被调用一次。

viewDidLoad方法在视图已经加载完成后会被调用,因为这个函数调用的时候,Controller的基本系统功能已经初始化完成,开发者一般会将一些Controller额外定义功能的初始化工作放在这个函数中。

viewWillAppear方法在视图即将显示的时候调用。

viewWillLayoutSubviews方法在视图将要布局其子视图时被调用。

viewDidLayoutSubviews方法在视图布局完成其子视图时被调用。

viewDidAppear方法在视图已经显示后被调用。

上面这些方法是UIViewController从创建到展现出来之间调用的生命周期函数,这些只是一半,UIViewController被释放和销毁的过程由如下方法完成:

● viewWillDisappear 方法在视图将要消失时调用,开发者可以在其中做一些数据清理的操作。

● viewDidDisappear方法在视图已经消失时被调用。

● dealloc 方法是对象的销毁方法,在对象被释放时调用,开发者可以通过在其中打印信息的方式检查一个类是否存在内存泄露等问题。

2.2.2 UIViewController的视图层级结构

UIViewController自带一个UIView类型的view,这个view平铺在屏幕上,是Controller的根视图,如果在Controller 中添加其他UI 控件都是添加在这个view 上,UIView 类通过addSubview方法来添加子view视图,子view视图也可以继续使用addSubview方法添加它自己的子视图,在第1章中,Hello World标签实际上就是添加在Controller的根view上的一个子Label视图,iOS系统通过这样的层级结构管理整个UI体系。