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学习的内容也是够多够呛的,消化过程中琢磨变成了博客中的该篇文章,在坚持中慢慢储备更多的知识吧!
我也知道文章写的不咋滴,就当做是俺做做笔记吧~