一、核心原则(先记牢)
- 所有生命周期方法都要先调用父类的实现 (比如
[super viewDidLoad]),且通常放在方法第一行。 - 不要手动调用生命周期方法(比如不要自己写
[self viewDidAppear:YES]),由系统自动触发。 - 按「初始化→视图加载→显示→布局→消失→销毁」的逻辑分配代码,避免功能混乱。
二、分场景使用(附完整示例)
下面是一个贴近实际开发的UIViewController示例,标注了每个生命周期方法的典型用法:
objective-c
#import <UIKit/UIKit.h>
@interface DemoViewController : UIViewController
// 定义需要管理的资源(定时器、监听、网络请求等)
@property (nonatomic, strong) NSTimer *updateTimer;
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
@end
@implementation DemoViewController
// 1. 初始化:仅做数据/属性初始化,不创建UI
- (instancetype)initWithTitle:(NSString *)title {
self = [super init];
if (self) {
// 初始化业务数据、配置项
self.title = title ?: @"默认标题";
NSLog(@"1. 初始化:仅初始化数据,不创建UI");
}
return self;
}
// 2. 视图加载完成:一次性创建UI、绑定数据(仅调用一次)
- (void)viewDidLoad {
[super viewDidLoad]; // 必须先调父类
NSLog(@"2. 视图加载完成:创建UI、初始化一次性资源");
// ✅ 典型用法1:设置视图基础样式
self.view.backgroundColor = [UIColor whiteColor];
// ✅ 典型用法2:创建UI控件并添加到视图
UIButton *testBtn = [UIButton buttonWithType:UIButtonTypeSystem];
testBtn.frame = CGRectMake(100, 200, 200, 50);
[testBtn setTitle:self.title forState:UIControlStateNormal];
[testBtn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:testBtn];
// ✅ 典型用法3:初始化网络请求(仅发起一次的请求,比如获取页面基础数据)
[self loadBaseData];
}
// 3. 视图即将显示:每次显示都要做的操作(比如刷新数据、开启监听)
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; // 必须先调父类
NSLog(@"3. 视图即将显示:刷新数据、开启定时器/监听");
// ✅ 典型用法1:刷新页面数据(比如从其他页面返回后更新)
[self refreshPageData];
// ✅ 典型用法2:开启定时器/通知监听
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];
// ✅ 典型用法3:设置导航栏/状态栏样式
self.navigationController.navigationBar.tintColor = [UIColor blueColor];
}
// 4. 视图布局:调整控件位置(适配屏幕旋转、尺寸变化)
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
NSLog(@"4. 视图即将布局:调整控件frame(可选)");
// ✅ 典型用法:手动调整控件布局(比如适配不同屏幕尺寸)
UIButton *testBtn = self.view.subviews.firstObject;
testBtn.centerXAnchor.constraintEqualToAnchor:self.view.centerXAnchor.active = YES; // 自动布局示例
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"5. 视图布局完成:获取控件最终尺寸");
// ✅ 典型用法:获取控件最终的frame/size(比如ScrollView的contentSize)
UIButton *testBtn = self.view.subviews.firstObject;
NSLog(@"按钮最终尺寸:%@", NSStringFromCGRect(testBtn.frame));
}
// 5. 视图已经显示:执行动画、请求权限(需要视图可见时操作)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"6. 视图已经显示:执行动画、请求权限");
// ✅ 典型用法1:执行视图动画
UIButton *testBtn = self.view.subviews.firstObject;
[UIView animateWithDuration:0.5 animations:^{
testBtn.transform = CGAffineTransformMakeScale(1.1, 1.1);
}];
// ✅ 典型用法2:请求权限(比如定位、相册,需要视图可见时弹框)
[self requestPhotoPermission];
}
// 6. 视图即将消失:清理临时资源(关闭定时器、移除监听)
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(@"7. 视图即将消失:关闭定时器、移除监听");
// ✅ 典型用法1:停止定时器
[self.updateTimer invalidate];
self.updateTimer = nil; // 置空避免野指针
// ✅ 典型用法2:移除通知监听
[[NSNotificationCenter defaultCenter] removeObserver:self];
// ✅ 典型用法3:取消未完成的网络请求
[self.dataTask cancel];
self.dataTask = nil;
// ✅ 典型用法4:隐藏键盘
[self.view endEditing:YES];
}
// 7. 视图已经消失:释放非必要资源(比如缓存、临时文件)
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
NSLog(@"8. 视图已经消失:释放临时资源");
// ✅ 典型用法:清理内存缓存
[[NSURLCache sharedURLCache] removeAllCachedResponses];
}
// 8. 内存警告:释放非核心资源(避免APP崩溃)
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
NSLog(@"9. 内存警告:释放非必要资源");
// ✅ 典型用法:释放图片缓存、非当前显示的数据
[[SDImageCache sharedImageCache] clearMemory]; // 第三方图片缓存示例
self.dataTask = nil; // 取消网络请求
}
// 9. 销毁:最终清理(移除所有强引用、避免内存泄漏)
- (void)dealloc {
NSLog(@"10. 控制器销毁:最终清理资源");
// ✅ 典型用法:最后兜底清理(比如确认定时器/监听已移除)
[self.updateTimer invalidate];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// --------------- 辅助方法(业务逻辑)---------------
- (void)loadBaseData {
// 初始化页面基础数据(仅调用一次)
self.dataTask = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://example.com/baseData"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 处理数据...
}];
[self.dataTask resume];
}
- (void)refreshPageData {
// 每次显示页面都刷新数据
NSLog(@"刷新页面数据");
}
- (void)timerAction {
// 定时器回调
NSLog(@"定时器执行:更新页面状态");
}
- (void)handleNotification:(NSNotification *)notification {
// 通知监听回调
}
- (void)requestPhotoPermission {
// 请求相册权限
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
// 处理权限结果...
}];
}
- (void)btnClick {
NSLog(@"按钮点击");
}
@end
三、常见错误(避坑指南)
- ❌ 不在
viewDidLoad里创建 UI,反而在init里创建:init阶段self.view还未加载,创建的 UI 无法添加到视图,会导致控件不显示。 - ❌ 忘记调用
super的生命周期方法:比如- (void)viewDidLoad { /* 没写[super viewDidLoad] */ },会导致系统默认逻辑失效(比如视图无法正常加载)。 - ❌ 在
viewWillAppear:里创建 UI:每次页面显示都会重复创建控件,导致内存泄漏、控件叠加。 - ❌ 没有在
viewWillDisappear:里停止定时器 / 移除监听:控制器销毁后定时器仍在运行,会导致野指针崩溃(经典内存泄漏场景)。
四、Swift 版本简化示例(适配 Swift 开发者)
如果用 Swift 开发,核心逻辑一致,语法稍有不同:
swift
import UIKit
class DemoViewController: UIViewController {
var updateTimer: Timer?
var dataTask: URLSessionDataTask?
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.title = "默认标题"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// 创建UI...
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
updateTimer?.invalidate()
updateTimer = nil
dataTask?.cancel()
dataTask = nil
}
@objc func timerAction() {
print("定时器执行")
}
deinit {
print("控制器销毁")
}
}
总结
- 分工明确 :
viewDidLoad做「一次性初始化(UI / 基础数据)」,viewWillAppear:做「每次显示都要刷新的操作」,viewWillDisappear:做「资源清理」。 - 资源闭环:开启的资源(定时器、监听、网络请求)必须在对应阶段关闭,避免内存泄漏。
- 遵循规范:必调父类方法、不手动触发生命周期方法,是避免崩溃的核心。