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

Objective-C 在 iOS 视图控制器管理中的实践

2022-04-113.5k 阅读

视图控制器基础概念

在 iOS 开发中,视图控制器(ViewController)是 MVC(Model - View - Controller)架构里的“C”,负责管理视图(View)以及处理业务逻辑。视图控制器是 iOS 应用界面构建和交互处理的核心组件。

Objective - C 作为 iOS 开发长期以来的主要编程语言,为视图控制器的管理提供了丰富且强大的功能。

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

在 Objective - C 中,创建一个视图控制器通常有两种常见方式:通过代码手动创建和使用 Interface Builder(XIB 或 Storyboard)。

手动创建视图控制器示例代码如下:

#import "MyViewController.h"

@implementation MyViewController

- (instancetype)init {
    self = [super init];
    if (self) {
        // 初始化相关属性
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // 视图加载后执行的操作,如添加子视图等
    UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    subView.backgroundColor = [UIColor redColor];
    [self.view addSubview:subView];
}
@end

上述代码创建了一个自定义的视图控制器 MyViewController,在 init 方法中进行一些基本属性初始化,viewDidLoad 方法用于在视图加载到内存后执行相关操作,这里添加了一个红色的子视图。

如果使用 Interface Builder 创建视图控制器,首先在 XIB 文件中设计视图布局,然后关联到相应的视图控制器类。例如,创建一个 MyViewController.xib 文件,在 Interface Builder 中拖拽视图组件并设置约束。在视图控制器类中通过以下方式加载 XIB:

#import "MyViewController.h"

@implementation MyViewController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // 初始化相关属性
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // 视图加载后执行的操作
}
@end

这里通过 initWithNibName:bundle: 方法从 XIB 文件初始化视图控制器。

视图控制器的生命周期

理解视图控制器的生命周期对于正确管理视图控制器至关重要。视图控制器的生命周期包含多个方法,每个方法在不同阶段被调用。

加载阶段

  1. loadView:此方法用于创建视图控制器的视图层次结构。如果通过代码手动创建视图,通常会重写此方法。例如:
- (void)loadView {
    UIView *mainView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    mainView.backgroundColor = [UIColor whiteColor];
    self.view = mainView;
}
  1. viewDidLoad:在视图加载到内存后调用,通常用于初始化视图相关的属性、添加子视图、设置约束等操作。如前面示例中在 viewDidLoad 方法添加子视图的操作。

显示阶段

  1. viewWillAppear::在视图即将显示到屏幕上时调用。可以在此方法中进行一些与显示相关的准备工作,比如设置导航栏标题、隐藏或显示某些视图元素等。
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.navigationItem.title = @"My View";
}
  1. viewDidAppear::在视图已经显示到屏幕上后调用。可以在此方法中执行一些需要在视图显示后立即执行的操作,比如启动动画等。

隐藏阶段

  1. viewWillDisappear::在视图即将从屏幕上消失时调用。可以在此方法中进行一些清理工作,比如停止正在运行的动画、取消网络请求等。
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    // 取消网络请求示例
    [self.networkTask cancel];
}
  1. viewDidDisappear::在视图已经从屏幕上消失后调用。

内存管理阶段

  1. didReceiveMemoryWarning:当系统内存不足时,视图控制器会收到此消息。可以在此方法中释放一些不必要的资源,比如缓存数据等。
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // 释放缓存图片
    self.cachedImage = nil;
}

视图控制器之间的导航与切换

在 iOS 应用中,通常会有多个视图控制器协同工作,这就涉及到视图控制器之间的导航与切换。

基于导航控制器(UINavigationController)的导航

导航控制器是一种用于管理一系列视图控制器的容器视图控制器,它提供了一种栈式的导航方式。

创建导航控制器并将根视图控制器推入栈中的示例代码如下:

#import "AppDelegate.h"
#import "RootViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    RootViewController *rootVC = [[RootViewController alloc] init];
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:rootVC];
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.rootViewController = navController;
    [self.window makeKeyAndVisible];
    return YES;
}
@end

在根视图控制器中,可以通过以下方式将新的视图控制器推送到导航栈中:

#import "RootViewController.h"
#import "DetailViewController.h"

@implementation RootViewController

- (void)pushToDetail {
    DetailViewController *detailVC = [[DetailViewController alloc] init];
    [self.navigationController pushViewController:detailVC animated:YES];
}
@end

通过 pushViewController:animated: 方法将 DetailViewController 推送到导航栈,用户可以通过导航栏的返回按钮或 popViewControllerAnimated: 方法返回上一级视图控制器。

基于标签栏控制器(UITabBarController)的切换

标签栏控制器用于在不同功能模块之间进行切换,它管理多个视图控制器,每个视图控制器对应一个标签。

创建标签栏控制器并添加子视图控制器的示例代码如下:

#import "AppDelegate.h"
#import "FirstViewController.h"
#import "SecondViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    FirstViewController *firstVC = [[FirstViewController alloc] init];
    UINavigationController *firstNav = [[UINavigationController alloc] initWithRootViewController:firstVC];
    firstNav.tabBarItem.title = @"First";
    firstNav.tabBarItem.image = [UIImage imageNamed:@"first_icon"];

    SecondViewController *secondVC = [[SecondViewController alloc] init];
    UINavigationController *secondNav = [[UINavigationController alloc] initWithRootViewController:secondVC];
    secondNav.tabBarItem.title = @"Second";
    secondNav.tabBarItem.image = [UIImage imageNamed:@"second_icon"];

    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    tabBarController.viewControllers = @[firstNav, secondNav];

    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.rootViewController = tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}
@end

在上述代码中,创建了两个视图控制器,并将它们分别包装在导航控制器中,然后添加到标签栏控制器的 viewControllers 数组中。用户可以通过点击标签栏的图标在不同的视图控制器之间进行切换。

模态视图控制器的展示与消失

模态视图控制器用于临时展示一个独立的视图控制器,通常用于需要用户进行特定操作的场景。

展示模态视图控制器的代码如下:

#import "ViewController.h"
#import "ModalViewController.h"

@implementation ViewController

- (void)showModal {
    ModalViewController *modalVC = [[ModalViewController alloc] init];
    modalVC.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:modalVC animated:YES completion:nil];
}
@end

通过 presentViewController:animated:completion: 方法展示模态视图控制器,并可以设置其 modalPresentationStyle 来决定展示样式。

关闭模态视图控制器可以在模态视图控制器内部通过以下代码实现:

#import "ModalViewController.h"

@implementation ModalViewController

- (void)dismissModal {
    [self dismissViewControllerAnimated:YES completion:nil];
}
@end

视图控制器之间的数据传递

在视图控制器之间切换时,常常需要传递数据。

从父视图控制器到子视图控制器传递数据

以基于导航控制器的导航为例,在父视图控制器中创建子视图控制器并传递数据的示例代码如下:

#import "ParentViewController.h"
#import "ChildViewController.h"

@implementation ParentViewController

- (void)pushToChild {
    ChildViewController *childVC = [[ChildViewController alloc] init];
    childVC.data = @"This is data from parent";
    [self.navigationController pushViewController:childVC animated:YES];
}
@end

ChildViewController 中定义接收数据的属性:

#import <UIKit/UIKit.h>

@interface ChildViewController : UIViewController

@property (nonatomic, strong) NSString *data;

@end

从子视图控制器到父视图控制器传递数据

可以通过代理模式实现从子视图控制器到父视图控制器传递数据。首先定义代理协议,在子视图控制器中声明代理属性并调用代理方法传递数据:

#import <UIKit/UIKit.h>

@protocol ChildViewControllerDelegate <NSObject>

- (void)childViewController:(ChildViewController *)child didFinishWithData:(NSString *)data;

@end

@interface ChildViewController : UIViewController

@property (nonatomic, weak) id<ChildViewControllerDelegate> delegate;

@end

@implementation ChildViewController

- (void)sendDataBack {
    if ([self.delegate respondsToSelector:@selector(childViewController:didFinishWithData:)]) {
        [self.delegate childViewController:self didFinishWithData:@"Data from child"];
    }
}
@end

在父视图控制器中遵循代理协议并实现代理方法接收数据:

#import "ParentViewController.h"
#import "ChildViewController.h"

@interface ParentViewController () <ChildViewControllerDelegate>

@end

@implementation ParentViewController

- (void)pushToChild {
    ChildViewController *childVC = [[ChildViewController alloc] init];
    childVC.delegate = self;
    [self.navigationController pushViewController:childVC animated:YES];
}

- (void)childViewController:(ChildViewController *)child didFinishWithData:(NSString *)data {
    NSLog(@"Received data from child: %@", data);
}
@end

兄弟视图控制器之间传递数据

兄弟视图控制器之间传递数据可以通过它们共同的父视图控制器作为中介来实现。例如,有 Brother1ViewControllerBrother2ViewController,它们的父视图控制器是 ParentViewController。在 Brother1ViewController 中通过父视图控制器将数据传递给 Brother2ViewController

#import "Brother1ViewController.h"
#import "ParentViewController.h"
#import "Brother2ViewController.h"

@implementation Brother1ViewController

- (void)sendDataToBrother2 {
    ParentViewController *parentVC = (ParentViewController *)self.parentViewController;
    Brother2ViewController *brother2VC = [parentVC.viewControllers objectAtIndex:1];
    brother2VC.data = @"Data from brother 1";
}
@end

Brother2ViewController 中定义接收数据的属性:

#import <UIKit/UIKit.h>

@interface Brother2ViewController : UIViewController

@property (nonatomic, strong) NSString *data;

@end

视图控制器的内存管理

在 iOS 开发中,正确管理视图控制器的内存至关重要,以避免内存泄漏和应用性能问题。

强引用循环

强引用循环是导致内存泄漏的常见原因之一。例如,视图控制器 A 持有视图控制器 B 的强引用,而视图控制器 B 又持有视图控制器 A 的强引用,这样就形成了强引用循环。

在使用代理模式时,如果不小心将代理属性设置为 strong 而不是 weak,就可能导致强引用循环。例如:

// 错误示例,导致强引用循环
@interface ChildViewController : UIViewController

@property (nonatomic, strong) id<ChildViewControllerDelegate> delegate;

@end

正确的做法是将代理属性设置为 weak

@interface ChildViewController : UIViewController

@property (nonatomic, weak) id<ChildViewControllerDelegate> delegate;

@end

通知中心与内存管理

当视图控制器注册了通知中心的通知时,如果在视图控制器销毁时没有注销通知,可能会导致内存泄漏。例如:

@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"SomeNotification" object:nil];
}

- (void)handleNotification:(NSNotification *)notification {
    // 处理通知
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SomeNotification" object:nil];
}
@end

viewDidLoad 方法中注册通知,在 dealloc 方法中注销通知,以确保在视图控制器销毁时不会因为通知中心的引用而导致内存泄漏。

避免过度保留视图控制器

在某些情况下,可能会意外地过度保留视图控制器。例如,将视图控制器添加到一个全局数组中并且没有及时移除,导致视图控制器无法被释放。要注意合理管理视图控制器的引用,确保在不需要时能够正确释放内存。

视图控制器管理中的高级技巧

自定义容器视图控制器

容器视图控制器允许在一个视图控制器中管理多个子视图控制器,提供了更灵活的视图管理方式。

创建自定义容器视图控制器的步骤如下:

  1. 创建一个继承自 UIViewController 的自定义容器视图控制器类,例如 CustomContainerViewController
  2. CustomContainerViewController 中添加子视图控制器并管理其视图布局。例如:
#import "CustomContainerViewController.h"
#import "ChildViewController1.h"
#import "ChildViewController2.h"

@implementation CustomContainerViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    ChildViewController1 *child1 = [[ChildViewController1 alloc] init];
    [self addChildViewController:child1];
    [self.view addSubview:child1.view];
    child1.view.frame = CGRectMake(0, 0, self.view.bounds.size.width / 2, self.view.bounds.size.height);
    [child1 didMoveToParentViewController:self];

    ChildViewController2 *child2 = [[ChildViewController2 alloc] init];
    [self addChildViewController:child2];
    [self.view addSubview:child2.view];
    child2.view.frame = CGRectMake(self.view.bounds.size.width / 2, 0, self.view.bounds.size.width / 2, self.view.bounds.size.height);
    [child2 didMoveToParentViewController:self];
}
@end

在上述代码中,CustomContainerViewController 添加了两个子视图控制器 ChildViewController1ChildViewController2,并分别设置了它们的视图布局。

视图控制器的转场动画

可以通过自定义转场动画来增强应用的用户体验。iOS 提供了 UIViewControllerAnimatedTransitioning 协议来实现自定义转场动画。

例如,创建一个淡入淡出的转场动画:

  1. 创建一个实现 UIViewControllerAnimatedTransitioning 协议的类,例如 FadeTransitionAnimator
#import <UIKit/UIKit.h>

@interface FadeTransitionAnimator : NSObject <UIViewControllerAnimatedTransitioning>

@end

@implementation FadeTransitionAnimator

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.5;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toVC.view];
    toVC.view.alpha = 0.0;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromVC.view.alpha = 0.0;
        toVC.view.alpha = 1.0;
    } completion:^(BOOL finished) {
        [fromVC.view removeFromSuperview];
        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];
}
@end
  1. 在视图控制器中使用这个转场动画:
#import "ViewController.h"
#import "FadeTransitionAnimator.h"
#import "DetailViewController.h"

@interface ViewController () <UIViewControllerTransitioningDelegate>

@end

@implementation ViewController

- (void)showDetail {
    DetailViewController *detailVC = [[DetailViewController alloc] init];
    detailVC.transitioningDelegate = self;
    detailVC.modalPresentationStyle = UIModalPresentationCustom;
    [self presentViewController:detailVC animated:YES completion:nil];
}

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return [[FadeTransitionAnimator alloc] init];
}

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    return [[FadeTransitionAnimator alloc] init];
}
@end

在上述代码中,ViewController 遵循 UIViewControllerTransitioningDelegate 协议,通过 animationControllerForPresentedController:presentingController:sourceController:animationControllerForDismissedController: 方法返回自定义的转场动画对象 FadeTransitionAnimator,从而实现淡入淡出的转场效果。

视图控制器的状态恢复

在应用被终止或内存不足时,iOS 提供了视图控制器状态恢复机制,允许保存和恢复视图控制器的状态。

  1. 启用状态恢复:在 AppDelegate 中启用状态恢复:
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder {
    return YES;
}

- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder {
    return YES;
}
  1. 视图控制器实现状态保存和恢复方法:
@implementation MyViewController

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
    [super encodeRestorableStateWithCoder:coder];
    // 保存自定义属性
    [coder encodeObject:self.customData forKey:@"CustomData"];
}

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
    [super decodeRestorableStateWithCoder:coder];
    // 恢复自定义属性
    self.customData = [coder decodeObjectForKey:@"CustomData"];
}

+ (NSString *)restorationIdentifier {
    return @"MyViewControllerIdentifier";
}

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
    return [[self alloc] init];
}
@end

在上述代码中,MyViewController 实现了 encodeRestorableStateWithCoder:decodeRestorableStateWithCoder: 方法来保存和恢复自定义属性,通过 restorationIdentifier 方法设置唯一标识符,并实现 viewControllerWithRestorationIdentifierPath:coder: 方法来创建恢复的视图控制器实例。

通过以上对 Objective - C 在 iOS 视图控制器管理中的各个方面的实践介绍,开发者可以更好地利用 Objective - C 进行 iOS 应用的视图控制器管理,构建出功能丰富、性能优良且用户体验良好的 iOS 应用。无论是基础的视图控制器创建与生命周期管理,还是高级的转场动画和状态恢复等功能,Objective - C 都提供了强大的支持和灵活的实现方式。在实际开发中,根据应用的需求合理运用这些知识和技巧,能够有效提升开发效率和应用质量。