面试题答案
一键面试加载数据
- 按需加载:
- 策略:只加载当前屏幕显示及附近少量的单元格数据,当用户滚动时,再加载新的数据。这样可以避免一次性加载大量数据导致的内存占用过高。
- 代码实现:在
UICollectionViewDataSource
的collectionView:cellForItemAtIndexPath:
方法中,通过判断indexPath
是否接近数据末尾,来决定是否加载新的数据。例如:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { // 假设dataArray是存储数据的数组 if (indexPath.row >= self.dataArray.count - 5) { // 加载新数据 [self loadMoreData]; } UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CellIdentifier" forIndexPath:indexPath]; // 配置单元格数据 return cell; } - (void)loadMoreData { // 从数据源(如网络、本地数据库)加载新数据,并添加到dataArray中 NSArray *newData = [self fetchNewData]; [self.dataArray addObjectsFromArray:newData]; // 刷新UICollectionView [self.collectionView reloadData]; }
- 异步加载:
- 策略:使用
NSOperationQueue
或Grand Central Dispatch (GCD)
在后台线程加载数据,避免阻塞主线程,确保界面的流畅性。 - 代码实现:以GCD为例:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSArray *data = [self loadDataFromDataSource]; dispatch_async(dispatch_get_main_queue(), ^{ self.dataArray = data; [self.collectionView reloadData]; }); });
- 策略:使用
重用机制
- 正确使用重用标识符:
- 策略:为
UICollectionViewCell
设置唯一的重用标识符,并在dequeueReusableCellWithReuseIdentifier:forIndexPath:
方法中使用该标识符获取可重用的单元格。 - 代码实现:
// 注册单元格类 [self.collectionView registerClass:[CustomCollectionViewCell class] forCellWithReuseIdentifier:@"CellIdentifier"]; - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CellIdentifier" forIndexPath:indexPath]; // 配置单元格数据 return cell; }
- 策略:为
- 自定义重用逻辑:
- 策略:对于复杂的单元格,可能需要自定义重用逻辑,确保重用的单元格状态正确。例如,单元格中有一个开关,重用时需要确保开关状态与当前数据匹配。
- 代码实现:在
UICollectionViewCell
子类中,添加一个方法用于重置单元格状态。
@interface CustomCollectionViewCell : UICollectionViewCell - (void)resetCellState; @end @implementation CustomCollectionViewCell - (void)resetCellState { // 重置开关状态等 self.switchControl.on = NO; } @end - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CellIdentifier" forIndexPath:indexPath]; [cell resetCellState]; // 根据数据配置单元格 if (self.dataArray[indexPath.row].isOn) { cell.switchControl.on = YES; } return cell; }
渲染优化
- 减少视图层级:
- 策略:尽量减少
UICollectionViewCell
中的视图层级,避免复杂的嵌套视图结构,这样可以提高渲染效率。 - 代码实现:例如,原本使用
UIView
嵌套多个UILabel
,可以考虑使用NSAttributedString
和一个UILabel
来实现同样的显示效果。
// 原本复杂的视图层级 UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, cellWidth, cellHeight)]; UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 20)]; UILabel *subtitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 35, 100, 20)]; [containerView addSubview:titleLabel]; [containerView addSubview:subtitleLabel]; [cell.contentView addSubview:containerView]; // 优化后 UILabel *combinedLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 55)]; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"Title\nSubtitle"]; [attributedString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:16] range:NSMakeRange(0, 5)]; [attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(6, 8)]; combinedLabel.attributedText = attributedString; [cell.contentView addSubview:combinedLabel];
- 策略:尽量减少
- 使用异步绘制:
- 策略:对于复杂的绘制任务,如自定义图形绘制,使用
Core Graphics
在后台线程进行绘制,然后将绘制好的图像显示在UICollectionViewCell
上。 - 代码实现:利用
UIGraphicsBeginImageContextWithOptions
在后台线程创建上下文进行绘制,然后切换回主线程设置图像。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ UIGraphicsBeginImageContextWithOptions(cellSize, NO, 0.0); CGContextRef context = UIGraphicsGetCurrentContext(); // 进行复杂绘制 CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor); CGContextFillRect(context, CGRectMake(0, 0, cellSize.width, cellSize.height)); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_async(dispatch_get_main_queue(), ^{ cell.backgroundImageView.image = image; }); });
- 策略:对于复杂的绘制任务,如自定义图形绘制,使用
内存释放
- 数据清理:
- 策略:当
UICollectionView
不再需要某些数据时,及时从数据源中移除这些数据,以释放内存。例如,当用户切换到其他界面,UICollectionView
所在视图即将消失时,清理不必要的数据。 - 代码实现:在视图控制器的
viewWillDisappear:
方法中进行数据清理。
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 清理数据 self.dataArray = nil; }
- 策略:当
- 避免循环引用:
- 策略:在使用代理、块等时,要注意避免循环引用,防止内存泄漏。例如,在使用块时,使用
__weak
修饰符。 - 代码实现:
__weak typeof(self) weakSelf = self; self.collectionView.scrollViewDidScrollBlock = ^(UIScrollView *scrollView) { __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf) { // 执行滚动相关操作 [strongSelf doSomethingWhenScroll]; } };
- 策略:在使用代理、块等时,要注意避免循环引用,防止内存泄漏。例如,在使用块时,使用