Objective-C中的视图控制器生命周期管理
视图控制器生命周期简介
在Objective-C开发中,视图控制器(UIViewController
)是iOS应用程序的核心组件之一,它负责管理应用程序界面的一个特定部分。视图控制器的生命周期涵盖了从创建、加载视图、显示视图到最终销毁的一系列过程。理解并正确管理这个生命周期对于编写高效、稳定且用户体验良好的iOS应用至关重要。
视图控制器的创建与初始化
初始化方法
在Objective-C中,UIViewController
有多种初始化方法。最常见的是init
方法,但通常我们会使用更具针对性的初始化方法,比如initWithNibName:bundle:
。这个方法允许我们指定关联的nib文件(用于界面布局)以及其所属的bundle。
MyViewController *myVC = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
上述代码中,我们创建了一个MyViewController
实例,并指定了与之关联的nib文件。如果nibName
为nil
,系统会尝试查找与视图控制器类名相同的nib文件。bundle
参数如果为nil
,则表示使用主bundle。
自定义初始化
除了系统提供的初始化方法,我们还可以在视图控制器类中定义自定义的初始化方法。例如,假设我们的视图控制器需要接收一个特定的数据对象进行初始化:
@interface MyViewController : UIViewController
@property (nonatomic, strong) NSString *dataString;
- (instancetype)initWithDataString:(NSString *)dataString;
@end
@implementation MyViewController
- (instancetype)initWithDataString:(NSString *)dataString {
self = [super initWithNibName:@"MyViewController" bundle:nil];
if (self) {
_dataString = dataString;
}
return self;
}
@end
在其他地方,我们可以这样使用这个自定义初始化方法:
MyViewController *myVC = [[MyViewController alloc] initWithDataString:@"Some important data"];
通过自定义初始化方法,我们可以在创建视图控制器时传入特定的数据,以便在后续的生命周期中使用。
视图加载
loadView方法
当视图控制器需要显示其视图时,首先会调用loadView
方法。这个方法的主要作用是创建视图层次结构。默认情况下,如果视图控制器是通过nib文件创建的,loadView
方法会自动从nib文件中加载视图。然而,如果我们需要以编程方式创建视图,就需要重写loadView
方法。
@implementation MyViewController
- (void)loadView {
UIView *mainView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
mainView.backgroundColor = [UIColor whiteColor];
self.view = mainView;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
label.text = @"Hello, World!";
[mainView addSubview:label];
}
@end
在上述代码中,我们重写了loadView
方法,以编程方式创建了一个白色背景的主视图,并在主视图上添加了一个标签。
viewDidLoad方法
在loadView
方法完成视图加载后,会紧接着调用viewDidLoad
方法。这个方法是我们进行视图相关配置的主要场所,例如设置视图的属性、添加手势识别器、初始化数据等。
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"My View Controller";
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(150, 200, 100, 40);
[button setTitle:@"Click Me" forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)buttonTapped {
NSLog(@"Button was tapped!");
}
@end
在viewDidLoad
方法中,我们设置了视图控制器的标题,并添加了一个按钮,同时为按钮添加了点击事件处理方法。
视图显示与消失
viewWillAppear:方法
当视图即将显示在屏幕上时,会调用viewWillAppear:
方法。在这个方法中,我们可以进行一些与视图显示相关的准备工作,比如更新视图的数据、调整视图的布局等。
@implementation MyViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSDate *currentDate = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString *dateString = [formatter stringFromDate:currentDate];
self.dateLabel.text = dateString;
}
@end
上述代码中,在视图即将显示时,我们更新了一个用于显示当前日期时间的标签。
viewDidAppear:方法
当视图已经完全显示在屏幕上后,会调用viewDidAppear:
方法。这个方法通常用于执行一些需要在视图显示后立即进行的操作,比如开始动画、启动定时器等。
@implementation MyViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[UIView animateWithDuration:1.0 animations:^{
self.someView.alpha = 0.5;
}];
}
@end
这里,我们在视图显示后,对一个视图进行了透明度渐变的动画。
viewWillDisappear:方法
当视图即将从屏幕上消失时,会调用viewWillDisappear:
方法。在这个方法中,我们可以进行一些清理工作,比如停止定时器、取消网络请求等。
@implementation MyViewController
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
@end
假设我们在视图显示期间启动了一个定时器,在视图即将消失时,我们需要停止并释放这个定时器。
viewDidDisappear:方法
当视图已经完全从屏幕上消失后,会调用viewDidDisappear:
方法。这个方法通常用于执行一些与视图消失后相关的最终操作,比如记录日志等。
@implementation MyViewController
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
NSLog(@"View has completely disappeared.");
}
@end
内存管理与视图控制器销毁
内存警告处理
在iOS设备内存紧张时,系统会向视图控制器发送内存警告。视图控制器可以通过实现didReceiveMemoryWarning
方法来处理内存警告。在这个方法中,我们应该释放一些可以重新创建的资源,以释放内存。
@implementation MyViewController
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if (self.isViewLoaded && self.view.window == nil) {
self.view = nil;
}
// 释放其他不必要的资源
self.largeDataArray = nil;
}
@end
上述代码中,如果视图当前没有显示在窗口中,我们释放视图以节省内存。同时,我们还释放了一个可能占用大量内存的数组。
视图控制器销毁
当视图控制器不再被需要时,会被系统销毁。在视图控制器销毁前,会调用其dealloc
方法。我们可以在dealloc
方法中进行一些最后的清理工作,比如取消所有的网络请求、释放对象等。
@implementation MyViewController
- (void)dealloc {
NSLog(@"MyViewController is being deallocated.");
[self.networkRequest cancel];
self.networkRequest = nil;
}
@end
在dealloc
方法中,我们取消了一个网络请求并释放了请求对象。需要注意的是,ARC(自动引用计数)环境下,大部分对象的内存管理由系统自动完成,但对于一些非ARC管理的资源,我们仍需要手动释放。
视图控制器的旋转与布局调整
设备方向改变
当设备的方向发生改变时,视图控制器会收到一系列的回调方法。在iOS 8及之后,我们主要通过viewWillTransitionToSize:withTransitionCoordinator:
方法来处理视图布局的调整。
@implementation MyViewController
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
self.imageView.frame = CGRectMake(100, 100, 200, 100);
} else {
self.imageView.frame = CGRectMake(50, 50, 100, 100);
}
} completion:nil];
}
@end
上述代码中,根据设备的横竖屏状态,我们调整了一个图片视图的位置和大小。
自适应布局
为了更好地支持不同设备的屏幕尺寸和方向,我们通常会使用自适应布局技术,如Auto Layout和Size Classes。在视图控制器中,我们可以通过设置视图的约束来实现自适应布局。
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *boxView = [[UIView alloc] init];
boxView.backgroundColor = [UIColor redColor];
[self.view addSubview:boxView];
boxView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:boxView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:boxView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:boxView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:100];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:boxView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:100];
[self.view addConstraints:@[centerXConstraint, centerYConstraint, widthConstraint, heightConstraint]];
}
@end
在上述代码中,我们创建了一个红色的视图,并通过Auto Layout约束将其居中显示,且设置了固定的宽度和高度。这样,无论设备方向如何改变,这个视图都会保持在合适的位置。
视图控制器之间的交互与生命周期影响
模态呈现
当一个视图控制器以模态方式呈现另一个视图控制器时,被呈现的视图控制器的生命周期会按照正常流程执行。而呈现视图控制器的viewWillDisappear:
和viewDidDisappear:
方法也会被调用,当被呈现的视图控制器消失后,呈现视图控制器的viewWillAppear:
和viewDidAppear:
方法会再次被调用。
// 呈现视图控制器中的代码
- (void)presentModalViewController {
ModalViewController *modalVC = [[ModalViewController alloc] initWithNibName:@"ModalViewController" bundle:nil];
[self presentViewController:modalVC animated:YES completion:nil];
}
// ModalViewController中的代码
@implementation ModalViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeSystem];
dismissButton.frame = CGRectMake(150, 200, 100, 40);
[dismissButton setTitle:@"Dismiss" forState:UIControlStateNormal];
[dismissButton addTarget:self action:@selector(dismissViewController) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:dismissButton];
}
- (void)dismissViewController {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
上述代码展示了一个视图控制器以模态方式呈现另一个视图控制器,并在被呈现的视图控制器中提供了一个按钮用于关闭自身。在这个过程中,两个视图控制器的生命周期方法会按照相应顺序被调用。
导航控制器
在导航控制器的场景下,当一个视图控制器被压入导航栈时,其viewWillAppear:
和viewDidAppear:
方法会被调用,而当前显示的视图控制器(即将被覆盖的视图控制器)的viewWillDisappear:
和viewDidDisappear:
方法会被调用。当从导航栈中弹出一个视图控制器时,被弹出的视图控制器的viewWillDisappear:
和viewDidDisappear:
方法会被调用,而之前被覆盖的视图控制器的viewWillAppear:
和viewDidAppear:
方法会再次被调用。
// 在导航控制器的根视图控制器中
- (void)pushViewController {
SecondViewController *secondVC = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
[self.navigationController pushViewController:secondVC animated:YES];
}
// SecondViewController中的代码
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *popButton = [UIButton buttonWithType:UIButtonTypeSystem];
popButton.frame = CGRectMake(150, 200, 100, 40);
[popButton setTitle:@"Pop" forState:UIControlStateNormal];
[popButton addTarget:self action:@selector(popViewController) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:popButton];
}
- (void)popViewController {
[self.navigationController popViewControllerAnimated:YES];
}
@end
通过上述代码,我们可以看到在导航控制器中,视图控制器的压入和弹出操作对其生命周期的影响。
总结视图控制器生命周期管理的要点
- 初始化与创建:选择合适的初始化方法,确保在创建视图控制器时能够正确配置初始数据。自定义初始化方法可以方便地传入特定数据。
- 视图加载:
loadView
方法用于创建视图层次结构,而viewDidLoad
方法用于配置视图属性和初始化数据。重写loadView
方法时要注意正确设置self.view
。 - 视图显示与消失:
viewWillAppear:
和viewDidAppear:
方法用于视图显示前和显示后的操作,viewWillDisappear:
和viewDidDisappear:
方法用于视图消失前和消失后的操作。在这些方法中要合理进行数据更新、动画启动与停止、资源清理等操作。 - 内存管理:通过
didReceiveMemoryWarning
方法处理内存警告,释放不必要的资源。在dealloc
方法中进行最后的清理工作,特别是对于非ARC管理的资源。 - 布局调整:利用
viewWillTransitionToSize:withTransitionCoordinator:
方法处理设备方向改变时的布局调整,结合Auto Layout和Size Classes实现自适应布局。 - 视图控制器交互:在模态呈现和导航控制器等场景下,要清楚不同视图控制器生命周期方法的调用顺序,以便正确处理数据传递和界面更新。
正确管理视图控制器的生命周期是iOS开发中保证应用程序稳定性、性能和用户体验的关键。开发者需要深入理解每个生命周期方法的作用和调用时机,并根据具体的业务需求合理地进行代码编写。通过合理运用这些知识,我们能够开发出更加健壮、高效且用户友好的iOS应用程序。