MST

星途 面试题库

面试题:Objective-C下UITableView性能优化策略探讨

当UITableView中有大量数据时,性能优化至关重要。请阐述在Objective-C开发环境下,针对UITableView有哪些有效的性能优化策略,比如如何处理重用机制、异步加载图片等,详细说明每一种策略的实现思路和代码实现要点。
24.5万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

重用机制

  1. 实现思路:UITableView 提供了单元格重用机制,通过复用已移出屏幕的单元格,避免频繁创建新单元格,从而节省内存和提高性能。
  2. 代码实现要点
    • UITableViewDataSource 协议的 tableView:cellForRowAtIndexPath: 方法中使用 dequeueReusableCellWithIdentifier: 方法来获取可复用的单元格。
    • 为每个单元格类型设置唯一的标识符(identifier)。例如:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"MyCellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    // 配置单元格内容
    cell.textLabel.text = [self.dataArray objectAtIndex:indexPath.row];
    return cell;
}

异步加载图片

  1. 实现思路:避免在主线程中加载图片,因为图片加载可能是耗时操作,会阻塞主线程,导致界面卡顿。使用异步加载的方式,在后台线程加载图片,加载完成后回到主线程更新 UI。
  2. 代码实现要点
    • 可以使用 NSURLSession 进行网络图片加载,配合 NSOperationQueue 实现异步加载。例如,使用 AFNetworking 框架(较常用):
#import <AFNetworking.h>

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"MyCellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    // 获取图片 URL
    NSURL *imageURL = [NSURL URLWithString:self.imageURLs[indexPath.row]];
    // 使用 AFNetworking 加载图片
    [[SDWebImageManager sharedManager] loadImageWithURL:imageURL options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                // 确保是当前 indexPath 的 cell
                if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
                    cell.imageView.image = image;
                }
            });
        }
    }];
    
    return cell;
}
  • 若不使用第三方框架,使用 NSURLSession 原生实现:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"MyCellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    NSURL *imageURL = [NSURL URLWithString:self.imageURLs[indexPath.row]];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:imageURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data &&!error) {
            UIImage *image = [UIImage imageWithData:data];
            dispatch_async(dispatch_get_main_queue(), ^{
                if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
                    cell.imageView.image = image;
                }
            });
        }
    }];
    [task resume];
    
    return cell;
}

减少视图渲染

  1. 实现思路:尽量减少单元格内视图的数量和复杂度,避免复杂的视图层级和透明视图,因为这些都会增加渲染的开销。
  2. 代码实现要点
    • 例如,若单元格只需要简单的文本和图片展示,不要添加多余的装饰视图。如果必须有复杂视图,考虑将其绘制到 UIImage 上,然后作为一个简单的 UIImageView 展示在单元格中。例如:
// 假设要绘制一个带渐变背景和文本的复杂视图
- (UIImage *)createComplexImage {
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 50), NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 绘制渐变背景
    CGGradientRef gradient;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = {1.0, 0.0, 0.0, 1.0,  // 起始颜色(红色)
                            0.0, 1.0, 0.0, 1.0}; // 结束颜色(绿色)
    CGFloat locations[] = {0.0, 1.0};
    gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
    CGContextDrawLinearGradient(context, gradient, CGPointMake(0, 0), CGPointMake(100, 50), 0);
    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorSpace);
    
    // 绘制文本
    NSString *text = @"Complex View";
    NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:16]};
    [text drawAtPoint:CGPointMake(10, 10) withAttributes:attributes];
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

然后在单元格中使用这个 UIImage

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"MyCellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    cell.imageView.image = [self createComplexImage];
    return cell;
}

预计算高度

  1. 实现思路:如果单元格高度是动态的,提前计算好每个单元格的高度并缓存起来,避免在 tableView:heightForRowAtIndexPath: 方法中每次都进行计算,减少开销。
  2. 代码实现要点
    • 在数据源加载完成后,遍历数据并计算每个单元格高度,存储在一个数组中。例如:
- (void)loadData {
    // 假设从网络获取数据
    NSArray *newData = [self fetchDataFromNetwork];
    self.dataArray = newData;
    
    // 预计算高度
    NSMutableArray *heightArray = [NSMutableArray arrayWithCapacity:self.dataArray.count];
    for (int i = 0; i < self.dataArray.count; i++) {
        // 根据数据计算高度,这里假设简单计算
        CGFloat height = [self calculateHeightForData:self.dataArray[i]];
        [heightArray addObject:@(height)];
    }
    self.heightCacheArray = heightArray;
}

- (CGFloat)calculateHeightForData:(id)data {
    // 实际根据数据内容计算高度,例如文本高度等
    NSString *text = data;
    CGSize size = [text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]}];
    return size.height + 20;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [self.heightCacheArray[indexPath.row] floatValue];
}

按需加载数据

  1. 实现思路:不要一次性加载所有数据,而是根据用户的滚动情况,当用户接近表格底部时,加载更多数据,这样可以减少内存占用和首次加载时间。
  2. 代码实现要点
    • 使用 UITableViewDelegatescrollViewDidScroll: 方法监听滚动事件,判断是否接近底部。例如:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGFloat offsetY = scrollView.contentOffset.y;
    CGFloat contentHeight = scrollView.contentSize.height;
    CGFloat scrollViewHeight = scrollView.frame.size.height;
    if (offsetY > (contentHeight - scrollViewHeight - 100)) {
        // 接近底部,加载更多数据
        [self loadMoreData];
    }
}

- (void)loadMoreData {
    // 从网络加载更多数据
    NSArray *newData = [self fetchMoreDataFromNetwork];
    // 将新数据添加到数据源数组
    [self.dataArray addObjectsFromArray:newData];
    // 刷新表格
    [self.tableView reloadData];
}