MST

星途 面试题库

面试题:Objective-C 自动布局下如何优化复杂视图层级的性能

假设你正在开发一个具有多层嵌套视图的应用界面,使用Objective-C的自动布局。随着视图层级的复杂,性能问题可能逐渐显现。请阐述你会采取哪些优化措施来提升自动布局在这种复杂场景下的性能,例如如何避免不必要的布局计算、如何合理设置约束等,并给出相应的代码示例或伪代码。
27.7万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

避免不必要的布局计算

  1. 减少动态更新频率: 尽量减少频繁更改视图约束或框架的操作。如果必须进行动态更新,尝试批量处理这些更改,而不是每次小变化都触发布局更新。 例如,假设我们有一个视图 myView,在一个循环中多次更改其约束:
// 不推荐,每次更改都会触发布局更新
for (int i = 0; i < 10; i++) {
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:myView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:i * 10];
    [myView addConstraint:constraint];
}

// 推荐,批量更新
[myView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSMutableArray *constraints = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:myView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:i * 10];
    [constraints addObject:constraint];
}
[myView addConstraints:constraints];
  1. 使用 layoutIfNeeded 时机: 仅在确实需要立即更新布局时调用 layoutIfNeeded。通常,系统会在合适的时机自动调用布局更新,不必要的调用会浪费性能。 例如,假设在视图加载后我们想执行一些初始化布局调整:
- (void)viewDidLoad {
    [super viewDidLoad];
    // 执行一些视图初始化和约束添加
    // 不要在这里立即调用 layoutIfNeeded,除非有特殊需求
}

// 当有明确的用户交互或其他操作需要立即更新布局时
- (IBAction)buttonTapped:(id)sender {
    // 更改约束
    NSLayoutConstraint *newConstraint = [NSLayoutConstraint constraintWithItem:self.someView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
    [self.someView addConstraint:newConstraint];
    [self.view layoutIfNeeded]; // 此时调用 layoutIfNeeded 以立即更新布局
}
  1. 延迟加载视图: 对于一些不马上需要显示的嵌套视图,延迟它们的加载和布局计算。可以使用 UIViewControllerloadViewIfNeeded 按需加载视图。 例如,在一个 UIViewController 子类中:
@interface MyViewController : UIViewController
@property (nonatomic, strong) UIView *lazyLoadedView;
@end

@implementation MyViewController
- (UIView *)lazyLoadedView {
    if (!_lazyLoadedView) {
        _lazyLoadedView = [[UIView alloc] init];
        // 添加约束等操作
        _lazyLoadedView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.view addSubview:_lazyLoadedView];
        NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:_lazyLoadedView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];
        NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:_lazyLoadedView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:10];
        [self.view addConstraints:@[leadingConstraint, topConstraint]];
    }
    return _lazyLoadedView;
}

- (void)someActionThatNeedsLazyView {
    [self.lazyLoadedView loadViewIfNeeded];
    // 对 lazyLoadedView 进行操作
}
@end

合理设置约束

  1. 保持约束简洁: 避免过度复杂或冗余的约束。例如,不要同时设置水平和垂直方向的固定尺寸以及与父视图的边缘约束,这样可能会导致约束冲突或不必要的计算。
// 不推荐,冗余约束
UIView *view = [[UIView alloc] init];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[superview addSubview:view];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:200];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100];
NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:10];
[superview addConstraints:@[widthConstraint, heightConstraint, leadingConstraint, topConstraint]];

// 推荐,更简洁的方式
UIView *view = [[UIView alloc] init];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[superview addSubview:view];
NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:10];
NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:-10];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-10];
[superview addConstraints:@[leadingConstraint, topConstraint, trailingConstraint, bottomConstraint]];
  1. 使用优先级: 合理设置约束优先级,确保在约束冲突时,重要的约束能够优先满足。例如,在一个包含标题和描述的视图中,标题可能更重要,应优先保持其布局。
UIView *titleView = [[UIView alloc] init];
UIView *descriptionView = [[UIView alloc] init];
[titleView setTranslatesAutoresizingMaskIntoConstraints = NO];
[descriptionView setTranslatesAutoresizingMaskIntoConstraints = NO];
[superview addSubviews:@[titleView, descriptionView]];

NSLayoutConstraint *titleLeadingConstraint = [NSLayoutConstraint constraintWithItem:titleView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];
NSLayoutConstraint *titleTopConstraint = [NSLayoutConstraint constraintWithItem:titleView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:10];
NSLayoutConstraint *descriptionTopConstraint = [NSLayoutConstraint constraintWithItem:descriptionView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titleView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10];
NSLayoutConstraint *descriptionLeadingConstraint = [NSLayoutConstraint constraintWithItem:descriptionView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];

// 设置标题约束优先级更高
titleLeadingConstraint.priority = UILayoutPriorityRequired;
titleTopConstraint.priority = UILayoutPriorityRequired;

[superview addConstraints:@[titleLeadingConstraint, titleTopConstraint, descriptionTopConstraint, descriptionLeadingConstraint]];
  1. 避免跨层级复杂约束: 尽量避免在深度嵌套的视图之间设置直接约束。如果可能,通过中间视图或容器视图来传递布局信息。 例如,有一个 A 视图包含 B 视图,B 视图又包含 C 视图,不建议直接从 AC 设置约束:
// 不推荐
UIView *viewA = [[UIView alloc] init];
UIView *viewB = [[UIView alloc] init];
UIView *viewC = [[UIView alloc] init];
[viewA setTranslatesAutoresizingMaskIntoConstraints = NO];
[viewB setTranslatesAutoresizingMaskIntoConstraints = NO];
[viewC setTranslatesAutoresizingMaskIntoConstraints = NO];
[viewA addSubview:viewB];
[viewB addSubview:viewC];
// 直接从 A 到 C 设置约束
NSLayoutConstraint *badConstraint = [NSLayoutConstraint constraintWithItem:viewC attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:viewA attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];
[viewA addConstraint:badConstraint];

// 推荐,通过 B 来传递布局信息
NSLayoutConstraint *bLeadingConstraint = [NSLayoutConstraint constraintWithItem:viewB attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:viewA attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];
NSLayoutConstraint *cLeadingConstraint = [NSLayoutConstraint constraintWithItem:viewC attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10];
[viewA addConstraint:bLeadingConstraint];
[viewB addConstraint:cLeadingConstraint];