MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的视图控制器生命周期管理

2022-06-186.6k 阅读

视图控制器生命周期简介

在Objective-C开发中,视图控制器(UIViewController)是iOS应用程序的核心组件之一,它负责管理应用程序界面的一个特定部分。视图控制器的生命周期涵盖了从创建、加载视图、显示视图到最终销毁的一系列过程。理解并正确管理这个生命周期对于编写高效、稳定且用户体验良好的iOS应用至关重要。

视图控制器的创建与初始化

初始化方法

在Objective-C中,UIViewController有多种初始化方法。最常见的是init方法,但通常我们会使用更具针对性的初始化方法,比如initWithNibName:bundle:。这个方法允许我们指定关联的nib文件(用于界面布局)以及其所属的bundle。

MyViewController *myVC = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];

上述代码中,我们创建了一个MyViewController实例,并指定了与之关联的nib文件。如果nibNamenil,系统会尝试查找与视图控制器类名相同的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

通过上述代码,我们可以看到在导航控制器中,视图控制器的压入和弹出操作对其生命周期的影响。

总结视图控制器生命周期管理的要点

  1. 初始化与创建:选择合适的初始化方法,确保在创建视图控制器时能够正确配置初始数据。自定义初始化方法可以方便地传入特定数据。
  2. 视图加载loadView方法用于创建视图层次结构,而viewDidLoad方法用于配置视图属性和初始化数据。重写loadView方法时要注意正确设置self.view
  3. 视图显示与消失viewWillAppear:viewDidAppear:方法用于视图显示前和显示后的操作,viewWillDisappear:viewDidDisappear:方法用于视图消失前和消失后的操作。在这些方法中要合理进行数据更新、动画启动与停止、资源清理等操作。
  4. 内存管理:通过didReceiveMemoryWarning方法处理内存警告,释放不必要的资源。在dealloc方法中进行最后的清理工作,特别是对于非ARC管理的资源。
  5. 布局调整:利用viewWillTransitionToSize:withTransitionCoordinator:方法处理设备方向改变时的布局调整,结合Auto Layout和Size Classes实现自适应布局。
  6. 视图控制器交互:在模态呈现和导航控制器等场景下,要清楚不同视图控制器生命周期方法的调用顺序,以便正确处理数据传递和界面更新。

正确管理视图控制器的生命周期是iOS开发中保证应用程序稳定性、性能和用户体验的关键。开发者需要深入理解每个生命周期方法的作用和调用时机,并根据具体的业务需求合理地进行代码编写。通过合理运用这些知识,我们能够开发出更加健壮、高效且用户友好的iOS应用程序。