面试题答案
一键面试一、视图控制器管理架构设计
-
基于导航控制器(UINavigationController)和标签栏控制器(UITabBarController)的基础架构
- 导航控制器:用于管理具有层级关系的视图控制器,实现页面的前进和后退。例如,在一个新闻应用中,从新闻列表页(一个视图控制器)点击进入新闻详情页(另一个视图控制器),就可以通过导航控制器来管理这种层级关系。
- 标签栏控制器:用于在不同功能模块间进行切换,比如社交应用中,通过标签栏切换“消息”“联系人”“动态”等不同模块。
-
自定义容器视图控制器(Container View Controller)
- 对于复杂的多层级嵌套场景,可以自定义容器视图控制器。在自定义容器视图控制器中,可以通过
addChildViewController:
方法添加子视图控制器,并通过transitionFromViewController:toViewController:duration:options:animations:completion:
方法来管理子视图控制器之间的切换动画。 - 例如,在一个电商应用中,商品详情页可能有多个子模块,如商品介绍、评论、规格选择等,这些子模块可以由不同的视图控制器管理,通过自定义容器视图控制器来管理它们的显示和切换。
- 对于复杂的多层级嵌套场景,可以自定义容器视图控制器。在自定义容器视图控制器中,可以通过
-
视图控制器工厂模式
- 创建一个视图控制器工厂类(如
ViewControllerFactory
),负责创建视图控制器实例。这样可以将视图控制器的创建逻辑集中管理,方便代码维护和复用。 - 例如:
- 创建一个视图控制器工厂类(如
@interface ViewControllerFactory : NSObject
+ (instancetype)sharedFactory;
- (UIViewController *)createViewControllerWithIdentifier:(NSString *)identifier;
@end
@implementation ViewControllerFactory
+ (instancetype)sharedFactory {
static ViewControllerFactory *factory = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
factory = [[ViewControllerFactory alloc] init];
});
return factory;
}
- (UIViewController *)createViewControllerWithIdentifier:(NSString *)identifier {
if ([identifier isEqualToString:@"HomeViewController"]) {
return [[HomeViewController alloc] initWithNibName:@"HomeViewController" bundle:nil];
} else if ([identifier isEqualToString:@"DetailViewController"]) {
return [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];
}
return nil;
}
@end
二、性能和可维护性优化
-
性能优化
- 延迟加载:对于不常用或加载耗时的视图控制器,采用延迟加载的方式。在需要显示该视图控制器时才进行创建和加载,避免在应用启动时就创建大量不必要的视图控制器,减少内存占用和启动时间。
- 视图重用:在自定义容器视图控制器或导航控制器管理的视图控制器栈中,对于已经创建过且符合复用条件的视图控制器,进行复用。例如,在一个聊天列表和聊天详情页的场景中,聊天详情页视图控制器在返回列表页后,可以将其保留在内存中,当下次再进入聊天详情页时,如果满足一定条件(如聊天对象相同等),可以复用之前的视图控制器实例。
- 优化视图切换动画:避免使用过于复杂或耗时的动画效果。可以使用系统提供的简单且高效的动画选项,如
UIViewAnimationOptionCurveEaseInOut
等,来实现流畅的视图切换动画。同时,控制动画的持续时间,不宜过长,以减少用户等待时间。
-
可维护性优化
- 模块化设计:将不同功能模块的视图控制器分开管理,每个模块有自己独立的视图控制器类和相关的业务逻辑。例如,将电商应用中的商品模块、订单模块、用户模块等的视图控制器分别放在不同的文件夹或命名空间中,便于代码的查找和修改。
- 清晰的代码结构:在每个视图控制器类中,按照功能将代码进行分组,如视图加载、数据请求、事件处理等。使用注释清晰地标明每个代码块的作用,提高代码的可读性。
- 依赖注入:通过依赖注入的方式,将视图控制器所依赖的对象(如数据模型、网络请求对象等)传递给视图控制器,而不是在视图控制器内部直接创建。这样可以提高代码的可测试性和可维护性,例如:
@interface DetailViewController : UIViewController
@property (nonatomic, strong) id<DataModelProtocol> dataModel;
- (instancetype)initWithDataModel:(id<DataModelProtocol>)dataModel;
@end
@implementation DetailViewController
- (instancetype)initWithDataModel:(id<DataModelProtocol>)dataModel {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_dataModel = dataModel;
}
return self;
}
@end
三、跨模块视图控制器间的通信
- 通知中心(NSNotificationCenter)
- 当一个视图控制器需要向其他多个视图控制器发送消息时,可以使用通知中心。例如,在一个多模块的应用中,用户在设置模块修改了主题颜色,需要通知所有涉及界面显示的模块更新颜色。
- 发送通知:
[[NSNotificationCenter defaultCenter] postNotificationName:@"ThemeColorChangedNotification" object:nil userInfo:@{@"themeColor": newThemeColor}];
- 接收通知:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateThemeColor:) name:@"ThemeColorChangedNotification" object:nil];
- (void)updateThemeColor:(NSNotification *)notification {
UIColor *themeColor = notification.userInfo[@"themeColor"];
// 更新界面颜色的逻辑
}
- 代理模式(Delegate Pattern)
- 当两个视图控制器之间存在明确的一对一通信关系时,适合使用代理模式。例如,在一个登录模块和主界面模块之间,登录成功后需要通知主界面模块进行用户信息的更新。
- 定义代理协议:
@protocol LoginViewControllerDelegate <NSObject>
- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)loginViewController;
@end
- 在登录视图控制器中设置代理并在登录成功时调用代理方法:
@interface LoginViewController : UIViewController
@property (nonatomic, weak) id<LoginViewControllerDelegate> delegate;
@end
@implementation LoginViewController
- (void)loginButtonTapped {
// 登录逻辑
if (loginSuccess) {
[self.delegate loginViewControllerDidLoginSuccessfully:self];
}
}
@end
- 在主界面视图控制器中遵循代理协议并实现代理方法:
@interface MainViewController : UIViewController <LoginViewControllerDelegate>
@end
@implementation MainViewController
- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)loginViewController {
// 更新用户信息的逻辑
}
@end
- 回调闭包(Block)
- 对于简单的跨模块视图控制器间的通信,特别是在有返回值的情况下,可以使用回调闭包。例如,在一个选择图片的视图控制器中,选择图片后返回图片数据给调用者视图控制器。
- 在选择图片视图控制器中定义闭包属性:
@interface ImagePickerViewController : UIViewController
@property (nonatomic, copy) void(^imagePickedBlock)(UIImage *pickedImage);
@end
@implementation ImagePickerViewController
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
UIImage *pickedImage = info[UIImagePickerControllerOriginalImage];
if (self.imagePickedBlock) {
self.imagePickedBlock(pickedImage);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
- 在调用者视图控制器中设置闭包并处理返回数据:
ImagePickerViewController *imagePickerVC = [[ImagePickerViewController alloc] init];
imagePickerVC.imagePickedBlock = ^(UIImage *pickedImage) {
// 处理选择的图片的逻辑
};
[self presentViewController:imagePickerVC animated:YES completion:nil];
四、视图控制器的动态创建与销毁
-
动态创建
- 使用前面提到的视图控制器工厂模式来动态创建视图控制器。根据不同的业务需求,通过调用工厂类的方法来创建相应的视图控制器实例。例如,在一个根据用户权限显示不同功能页面的应用中,根据用户权限的判断结果,调用
ViewControllerFactory
的createViewControllerWithIdentifier:
方法来创建对应的视图控制器。 - 在创建视图控制器时,同时设置好其所需的属性和依赖关系。如在创建商品详情视图控制器时,将商品的唯一标识或数据模型传递给该视图控制器,以便其正确显示商品详情信息。
- 使用前面提到的视图控制器工厂模式来动态创建视图控制器。根据不同的业务需求,通过调用工厂类的方法来创建相应的视图控制器实例。例如,在一个根据用户权限显示不同功能页面的应用中,根据用户权限的判断结果,调用
-
动态销毁
- 在视图控制器不再需要时,需要进行适当的销毁操作以释放内存。对于通过导航控制器管理的视图控制器,当调用
popViewControllerAnimated:
方法时,视图控制器会被自动从导航栈中移除并释放内存(前提是没有其他强引用指向该视图控制器)。 - 对于自定义容器视图控制器管理的子视图控制器,在移除子视图控制器时,需要调用
removeFromParentViewController
方法,并在子视图控制器的dealloc
方法中进行必要的资源释放操作,如取消网络请求、移除通知中心的观察者等。 - 例如:
- 在视图控制器不再需要时,需要进行适当的销毁操作以释放内存。对于通过导航控制器管理的视图控制器,当调用
- (void)removeChildViewController:(UIViewController *)childVC {
[childVC willMoveToParentViewController:nil];
[childVC.view removeFromSuperview];
[childVC removeFromParentViewController];
}
在子视图控制器的dealloc
方法中:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 取消网络请求等资源释放操作
}