IOS —— 控制器加载及UI控件初始化过程 及相关的那些事

还是喜欢说白话文的我!这回闲话少说进入正题


1.ViewController(控制器) 加载过程

我们知道,当我们需要跳转一个页面的时候,会新建一个Viewcontroller。创建一个链桥通过navigationController跳转过去。

那么这一个过程里究竟执行了什么方法发生了什么呢?

我们新建一个叫XgViewController的文件,并且创建一个xib文件。

这里我们重写一下init以及initWithNib的方法,并且对应在方法实际执行前打印一下当前执行的是什么方法

(__func__ 该参数可以直接打印出当前方法)

- (instancetype)init {
    
    NSLog(@"%s",__func__);
    
    self =  [super init];
    if (self) {
        
    }
    return self;
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    NSLog(@"%s",__func__);
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {

    }
    return self;
}

以下是执行的日志文件

2018-12-12 23:34:17.438869+0800 Second_Class[28117:2101088] -[XgViewController init]
2018-12-12 23:34:17.439035+0800 Second_Class[28117:2101088] -[XgViewController initWithNibName:bundle:]

当我们使用init方法创建控制器时,会自动的执行initWithNibName方法。这里说明了

init方法里头封装了initWithNibName方法。执行init方法时会自动执行后者。反之则不执行。

但此时问题出现了。重写方法后跳转页面是纯黑色的。意味着XgViewController的页面并没有初始化成功。

也说明了xib没有被找到。这是为什么?原因也很简单

原因在initWithNibName方法中

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

当方法中nibNameOrNil  = nil 的时候。方法默认会寻找与类名相同的xib文件。

当方法中nibBundleOrNil = nil 的时候。方法会默认寻找MainBudle。

当我们重写了方法,这些默认值也就烟消云散了。

这说明当自己需要控制一个xib页面时,应该将需要控制的代码细则写入initWithNibName中。

所以为了能正确的利用xib,创建方法应该这样写

 XgViewController *xgVC = [[XgViewController alloc] initWithNibName:@"XgViewController" bundle:nil];

这样跳转就没有黑屏了。问题1解决

那么继续回到我们加载过程中来。

我们继续在XgViewController中

对控制器生命周期中的个别方法以及默认封装的方法分别添加打印 __func__ 参数查看下他们执行的顺序

- (void)loadView

- (void)viewDidLoad 

- (void)viewWillAppear:(BOOL)animated

- (void)viewWillLayoutSubviews

- (void)viewDidAppear:(BOOL)animated

这时候有人问为什么是只弄这些方法不整生命周期中的其他方法呀?

因为在控制器的创建中,这些方法扮演者关键的作用

创建控制器 - > 读取窗口后 - >  窗口即将出现前 - > 窗口布局设置- > 窗口出现后

是这么一个流程

2018-12-12 23:46:29.377394+0800 Second_Class[28197:2114504] -[XgViewController loadView]
2018-12-12 23:46:29.381996+0800 Second_Class[28197:2114504] -[XgViewController viewDidLoad]
2018-12-12 23:46:29.382476+0800 Second_Class[28197:2114504] -[XgViewController viewWillAppear:]
2018-12-12 23:46:29.394225+0800 Second_Class[28197:2114504] -[XgViewController viewWillLayoutSubviews]
2018-12-12 23:46:29.896049+0800 Second_Class[28197:2114504] -[XgViewController viewDidAppear:]

其中在loadView中,我们通常用到的控制器中的self.view便是在此创建,当我们当前控制器需要用到指定的自定义的窗口时。重写该方法即可

这里我创建一个XgView

- (void)loadView
{
    [super loadView];
    self.view = [[XgView alloc] init];
}

就这么简单的一步,就完成了View与controller的分离。我们可以在xgView中自定义需要的元素,并且可以利用Controller通过监听xgView中的属性变化来做出对应的操作。

那loadView方法为空时,调用self.view会怎么样?

这里我们可以试着注释loadView中的数据启动下程序,结果会是怎么样呢

是死循环。

因为self.view本质为懒加载,当self.view为空时调用loadView方法。当loadView为空时将空的结果返回给self.view。

在过程中不断的打印着viewDidLoad方法。直至Xcode提示死循环报错。

 

那么接下来还是退出当前控制器。随之而来产生的一个问题是

当前页面的消失是在新页面出现前还是出现后呢?

真理在于实践。这里我们在当前页面的ViewDidDisappear: 、新页面中的ViewWillAppear: 分别打印一下__func__。这样是不是就一目了然了 

2018-12-13 00:08:12.863543+0800 Second_Class[28408:2147061] -[ViewController viewWillAppear:]
2018-12-13 00:08:13.368920+0800 Second_Class[28408:2147061] -[XgViewController viewDidDisappear:]

这是打印的结果。这时候很多人的就会想不明白,难道不是旧页面消失,新页面才会出现的吗。为什么会本末倒置呢?

这也是一道面试常问的题,为什么呢?

因为Controller(控制器)的切换本质上也就是View(窗口)的切换。View的切换过程中如果按照 旧页面消失 - > 新页面出现这样的方式来执行的话

在切换时,当新页面未加载出来时会出现一段无View的状态,整体呈黑色。造成的用户体验是极度不友好的。

所以当新View出现时,旧View才会消失。确保的是用户体验。


2.UI控件初始化过程

UI控件初始化过程与UIViewController一致,这里为了区分开以UIButton举例

UIButton在初始化过程中,是会自动执行init/initWithFrame 方法 。

执行init方法时自动执行initWithFrame,反之则不执行

同理,通过Xib创建UI控件时

nib在初始化过程中,是会自动执行initWithCoder/awakeFromXib 方法

既然和控制器初始化过程基本一致为什么要特地抽出一个模块来讲UI控件的初始化呢

原因在控件初始化的过程中有这么一个方法

这里依然是使用代码举例

- (void)viewDidLoad {
    [super viewDidLoad];
    _xgBtn = [[XgButton alloc] init];
    [self.view addSubview:_xgBtn];
   [self createButton];
}
- (void)createButton
{
    NSLog(@"1");
    [_xgBtn setNeedsLayout];
    NSLog(@"3");
}

在控制器中我实例化了一个xgBtn对象,并调用了xgBtn中的setNeedsLayout的方法。(方法中添加了输出打印"2"的语句)

执行setNeedsLayout方法等同于执行xgBtn中的layoutSubViews(初始化布局代码)

按照一般的逻辑来讲,打印顺序应该是1、2、3 按顺序依次打印才对

但是输出结果呢?

是1、3、2。

这时候有人会疑惑,我们不是按顺序执行吗。先打印1,然后利用setNeedsLayout布局页面,然后打印2,接下来打印3。

这样理解是没有错的,不过那是建立在xgBtn中的layoutSubViews方法在当前线程执行。

苹果官方因为担心页面数据加载与初始化影响app性能,在上期提到的runloop里,将初始化应用布局代码分开成俩个循环进行处理。

上述代码中所写到的[_xgBtn setNeedLayout];起到的作用仅仅是唤醒runloop,将该方法标记并且加入下一个处理的循环。

如果只是小项目并不在意性能方面的事情时,硬是要初始化与布局代码放在同一条线程中执行时。

可以对当前对象采用以下方法

 [_xgBtn layoutIfNeeded]; or [_xgBtn layoutSubviews] 

 将初始化插入当前的循环中。

 


3.UIView、CALayer的关系

CALayer和UIView中所实现的方法基本是一致,但不同的是

UIView可以响应事件,CALayer无法响应事件

UIView实际上就是CALayer的代理,是对CALayer方法的封装,充当CALayer的代理对象

UIView之所以能显示东西也是因为有CALayer的原因。

说响应事件可能还是有点含糊,所以俩者的区别是

UIView主要是对显示内容的管理

CALayer主要是对显示内容进行绘制

具体关于这个模块往后有时间抽出来单独举例子讲,这里不过多赘述


简单结语:今天在IOS学习的内容也是够多够呛的,消化过程中琢磨变成了博客中的该篇文章,在坚持中慢慢储备更多的知识吧!

我也知道文章写的不咋滴,就当做是俺做做笔记吧~ 

posted @ 2018-12-13 01:45  幽幽幽瓜  阅读(574)  评论(0编辑  收藏  举报