【iOS】loadView原理

我们一直在使用控制器,但是有没有想过控制器的view是如何创建的呢?

当外界第一次使用当前控制器的view时,会调用控制器的loadView方法,该方法用来创建控制器的view。控制器的view是懒加载的(什么时候调用,什么时候创建),如果已经创建就直接使用。

普通流程

创建一个控制器,并用IB描述控制器的view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// FirstViewController.h
#import <UIKit/UIKit.h>
@interface FirstViewController : UIViewController

@end

// FirstViewController.m
#import "FirstViewController.h"

@interface FirstViewController ()

@end

@implementation FirstViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}

@end

指定UIWindow的跟控制器为FirstViewController

1
2
3
4
5
6
7
8
9
10
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] init];
self.window.backgroundColor = [UIColor whiteColor];

FirstViewController *firstVC = [[FirstViewController alloc] init];
self.window.rootViewController = firstVC;

[self.window makeKeyAndVisible];
return YES;
}

编译运行,看效果:

这是正常我们使用控制器和IB的流程。控制器的view是在loadView方法中创建的,我们尝试重写该方法看下会有什么效果。

重写loadView

重写FirstViewControllerloadView方法

1
2
3
- (void)loadView {
NSLog(@"%s", __func__);
}

运行后,界面是一个空白,FirstView.xib也没有加载,控制台输出多次-[FirstViewController loadView]。查看视图层级关系后,发现控制器view不存在,如果我们再给UIWindow一个颜色,看到的就是一个UIWindow窗口。

1
2
3
self.window.backgroundColor = [UIColor redColor];
...
firstVC.view.backgroundColor = [UIColor blueColor];

效果:

结论: 子类重写loadView方法后,如果不做任何处理,控制器不会加载IB,更不会自动创建view。

思考:如果调用[super loadView],会走默认的流程么?
如果调用[super loadView],也仅仅是会自动创建view,不会加载IB,除非在控制器调用initWithNibName:初始化方法时指定IB名称。

手动创建控制器view

我们尝试自己创建view

1
2
3
4
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:self.view.frame];
self.view.backgroundColor = [UIColor yellowColor];
}

编译运行,程序崩溃了。

从上面堆栈信息可以看出,发生了死循环,为什么?
因为控制器view是懒加载的,只要调用viewget方法就会进入loadView方法。所以在view创建完成前不要使用使用他。

view懒加载伪代码:

1
2
3
4
5
6
- (UIView *)view {
if (!_view) {
[self loadView];
}
return _view;
}

修改view的创建方式

1
2
3
4
5
- (void)loadView {
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor yellowColor];
self.view = view;
}

编译运行,显示正常

loadView中不需要给自定义的view设置大小,在合适的时机(viewWillLayoutSubviewsviewDidLayoutSubviews)view会自动设置为当前屏幕大小。

查看viewDidLoad加载时机

AppDelegate.m

1
2
3
4
5
6
7
8
9
10
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] init];

FirstViewController *firstVC = [[FirstViewController alloc] init];
firstVC.view.backgroundColor = [UIColor redColor];
self.window.rootViewController = firstVC;

[self.window makeKeyAndVisible];
return YES;
}

FirstViewController.m

1
2
3
4
5
- (void)loadView {
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor yellowColor];
self.view = view;
}

思考:view是什么颜色?

红色,因为代码执行顺序是这样的:

  1. 执行firstVC.view.backgroundColor = [UIColor redColor];调用控制器view的get方法;
  2. 执行firstVCloadView方法;
  3. 创建控制器view,创建完成后执行view.backgroundColor = [UIColor yellowColor];
  4. loadView执行完毕,view的get方法执行结束;
  5. 颜色重新赋值firstVC.view.backgroundColor = [UIColor redColor];

viewDidLoad修改view的颜色

上面示例基础上,增加viewDidLoad方法

1
2
3
4
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blueColor];
}

思考:view是什么颜色?

红色,为什么还是红色呢?viewDidLoad不是最后才执行的么?我们看下代码的执行顺序。

AppDelegate.m

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] init];

FirstViewController *firstVC = [[FirstViewController alloc] init];
NSLog(@"1");
firstVC.view.backgroundColor = [UIColor redColor];
NSLog(@"2");
self.window.rootViewController = firstVC;
NSLog(@"3");
[self.window makeKeyAndVisible];
NSLog(@"4");
return YES;
}

FirstViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)loadView {
NSLog(@"5");
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor yellowColor];
self.view = view;
NSLog(@"6");
}

- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"7");
self.view.backgroundColor = [UIColor blueColor];
NSLog(@"8");
// Do any additional setup after loading the view.
}

顺序:1 > 5 > 6 > 7 > 8 > 2 > 3 > 4
看到执行loadView结束后执行了viewDidLoad,所以颜色变成了红色。

view懒加载伪代码:

1
2
3
4
5
6
7
- (UIView *)view {
if (!_view) {
[self loadView];
[self viewDidLoad];
}
return _view;
}

通过上面的分析可以总结:

  • 控制器的view是懒加载的;
  • 当控制器view创建完成后,会自动调用viewDidLoad方法。