viewDidLoad被提前调用

本文探讨了iOS开发中一个不为人知的现象:在特定条件下viewDidLoad方法的提前调用导致的执行顺序问题。该问题使得视图控制器内的成员变量在初始化前就被访问,引发程序逻辑错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天写项目的时候发现了一个非常奇怪的问题,我一度认为可能是我的 Xcode 出了 bug。

先来猜猜下面代码的执行顺序?



这是一段非常常见的代码,很多 controller 都是这么写的。

正常来说执行顺序肯定是 1、2、3、4 无疑。然而事实上,执行顺序却是:1、3、4、2。


这就导致了一个问题:viewModel 请求不到数据,因为走到第 4 步的时候,viewModel 还没有被初始化,此时它是 nil。

如下图所示:



从控制台中我们可以看到,最下面的 _viewModel = nil。而且当走完第 4 步后,程序竟然又回到了第 2 步。

起初我以为这是一个 Xcode 本身的 bug,因为它实在是太奇怪了。后来我才知道,原来是因为 viewDidLoad 被提前调用了。


viewDidLoad 方法一般只会在 nib 文件已载入内存,也就是视图树构建完成之后调用。

但是如果 controller 的 view 属性被使用时为 nil,就会导致 nib 的加载,从而导致 viewDidLoad 的调用。


对于本文来说,我们在初始化方法中写了 self.view.backgroundColor = xxx; 这句话本身并没有任何错误,但是它导致了 viewDidLoad 的提前调用,因此就出现了文章开头提到的情况,执行顺序变成了 1、3、4、2。


最后我的解决办法是,把成员变量 _viewModel 的初始化放到 viewDidLoad 里。这又是为什么呢?具体内容可以参考这篇文章:iOS 开发中的争议(一)类的成员变量应该如何定义?

大部分的类成员变量都需要在类初使化方法中赋值,大部分的 UIViewController 的成员变量,都需要在 viewDidLoad 方法中赋值。

<think>我们正在讨论iOS开发中UIViewController的viewDidLoad方法。根据引用[1]和引用[2],viewDidLoad是UIViewController生命周期中的一个重要方法。 引用[1]指出:无论是从xib文件加载视图还是自己创建视图,viewDidLoad方法都会被调用,而且是在视图已经被加载后调用。同时强调,任何UI控件的创建都不应该在viewDidLoad方法中实现,因为这是xib文件或者loadView方法的任务。viewDidLoad只是通过nib或者loadView方法加载视图的一种拓展和补充机制。 引用[2]详细描述了UIViewController的生命周期,其中viewDidLoad在loadView之后被调用,此时整个视图层次(view hierarchy)已经放到内存中,可以移除一些视图,修改约束,加载数据等。 因此,我们可以总结如下: 1. viewDidLoad调用时机:在视图控制器的视图被加载到内存之后调用,且在整个视图控制器的生命周期中只调用一次(除非视图被从内存中清除,但通常不会)。 2. 作用:用于进行一些初始化操作,但这些操作不应该包括创建视图层次(因为视图层次已经在loadView或从nib/storyboard加载完成),而应该是对已经加载的视图进行配置,例如设置控件的属性、加载数据、添加手势等。 3. 注意事项:避免在viewDidLoad中执行耗时操作,因为这会延迟视图的显示。如果确实需要执行耗时操作,应该使用异步方式。 使用场景: - 配置界面上的控件(例如设置文本、颜色、字体等) - 初始化数据模型,并更新界面 - 添加手势识别器 - 注册通知 - 设置委托 代码示例: ```objective-c - (void)viewDidLoad { [super viewDidLoad]; // 设置标题 self.title = @"首页"; // 设置背景色 self.view.backgroundColor = [UIColor whiteColor]; // 配置一个标签 UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, 200, 40)]; label.text = @"Hello, World!"; [self.view addSubview:label]; // 加载数据 [self loadData]; } - (void)loadData { // 异步加载数据 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 模拟网络请求 [NSThread sleepForTimeInterval:2.0]; dispatch_async(dispatch_get_main_queue(), ^{ // 更新UI }); }); } ``` 注意:在viewDidLoad中,视图的几何属性(如frame、bounds)可能还没有确定,因为此时还没有进行自动布局(Auto Layout)的计算。因此,如果代码依赖于视图的尺寸,最好在viewDidLayoutSubviews中处理。 另外,引用[1]特别强调,UI控件的创建不应该在viewDidLoad中,而应该在loadView方法中(如果是通过代码创建视图)或者由xib/storyboard完成。viewDidLoad只是对已经存在的视图进行配置。 总结:viewDidLoad是进行视图控制器初始化设置的重要方法,但要注意它只被调用一次,并且此时视图的几何属性可能还未确定。 相关问题: 1. viewDidLoad和loadView方法有什么区别? 2. 在viewDidLoad中为什么不能设置视图的frame? 3. 如果视图控制器是从storyboard创建的,viewDidLoad中应该做什么? 4. viewDidLoad和viewWillAppear的区别是什么? 5. 在viewDidLoad中执行网络请求是否合适?</think>### iOS开发中 `viewDidLoad` 方法详解 `viewDidLoad` 是 `UIViewController` 生命周期中的核心方法,在视图加载完成后触发。以下是详细解析: #### 1. **触发时机与作用** - **触发时机**:视图控制器完成视图加载后调用(在 `loadView` 方法之后)[^1][^2] - **关键特征**: ```mermaid graph LR A[initWithNibName] --> B[loadView] B --> C[viewDidLoad] C --> D[viewWillAppear] ``` - **作用**: - 执行**一次性初始化**操作 - 配置视图元素属性(颜色、字体等) - 加载初始数据 - 注册通知/委托 #### 2. **正确使用场景** ```objective-c - (void)viewDidLoad { [super viewDidLoad]; // ✅ 正确场景1:配置视图属性 self.titleLabel.textColor = [UIColor redColor]; self.tableView.delegate = self; // ✅ 正确场景2:初始化数据 [self loadInitialData]; // ✅ 正确场景3:添加手势识别 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap)]; [self.view addGestureRecognizer:tap]; // ✅ 正确场景4:注册通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; } ``` #### 3. **禁止操作与常见误区** - ❌ **禁止创建视图**: ```objective-c // 错误示例:创建新视图 UIView *newView = [[UIView alloc] init]; // 应在loadView中创建[^1] ``` - ❌ **避免布局操作**: ```objective-c // 错误示例:设置视图尺寸 self.button.frame = CGRectMake(0, 0, 100, 40); // 应在viewDidLayoutSubviews中处理[^2] ``` - ⚠️ **注意点**: - 每个视图控制器**仅调用一次**(除非手动释放视图) - 视图的几何属性(frame/bounds)尚未确定 - 不保证网络请求完成时界面已显示 #### 4. **与其他生命周期方法对比** | 方法 | 调用次数 | 典型用途 | 视图几何状态 | |---------------------|----------|-----------------------------------|--------------------| | `loadView` | 1次 | 创建视图层次 | 未设置 | | **`viewDidLoad`** | 1次 | 配置视图属性/加载数据 | 未确定 | | `viewWillAppear` | 多次 | 刷新数据/准备动画 | 已确定 | | `viewDidLayoutSubviews` | 多次 | 最终布局调整 | 最终确定 | #### 5. **最佳实践建议** 1. **保持轻量化**: ```objective-c // 耗时操作异步执行 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *data = [NSData dataWithContentsOfURL:imageURL]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = [UIImage imageWithData:data]; }); }); ``` 2. **内存管理**: ```objective-c - (void)dealloc { // 必须移除观察者 [[NSNotificationCenter defaultCenter] removeObserver:self]; } ``` 3. **状态恢复**: ```objective-c - (void)encodeRestorableStateWithCoder:(NSCoder *)coder { [super encodeRestorableStateWithCoder:coder]; [coder encodeObject:self.dataArray forKey:@"data"]; } ``` #### 6. **调试技巧** ```objective-c - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"%@: viewDidLoad", self.class); // 检查视图层次 NSLog(@"View hierarchy: %@", self.view.subviews); } ``` > **关键总结**:`viewDidLoad` 是视图初始化的**核心枢纽**,适合执行**一次性配置**,但需避免视图创建和布局操作。它确保视图层次已加载,但尚未显示到屏幕[^1][^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值