开发中常用到UITableView加载大量数据,需要性能优化问题,本文通过UITableView加载大量图片案例来说明。
- 实现方式一:基于可见cell视图加载图片
实现原理: tableView加载可见的cell图片(手指未拖动和减速动画快结束才加载)
(1)绑定cell时,判断手指未拖动和减速动画快结束,才加载图片
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellId"];
if (cell==nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellId"];
}
ImageRecord *record = self.dataList[indexPath.row];
if (record.image) {
cell.imageView.image = record.image;
}else{
cell.imageView.image = [UIImage imageNamed:@"head"];
//手指未拖动和减速动画快结束
if (!tableView.dragging && !tableView.decelerating) {
//加载图片
[self downloadImage:indexPath];
}
}
cell.textLabel.text = record.title;
return cell;
}
(2)tableview停止滚动,开始加载图像,实现以下代理方法
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
//加载可见cell图片
[self showVisibleCellImage];
}
(3)tableView停止拖拽,没有减速动画,实现以下代理方法
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
//加载可见cell图片
if (!decelerate) {
[self showVisibleCellImage];
}
}
(4)退出界面时,取消任务下载
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
//取消下载图片任务
NSArray *values = self.tasksDic.allValues;
[values makeObjectsPerformSelector:@selector(cancel)];
}
2.实现方式二:基于runloop实现图片渲染
原理:创建定时器,获取当前线程runloop ,并监听runloop将进入休眠事件,处理图片加载,runloop每次循环时,只加载一次图片渲染。
(1)创建定时器
// 让runloop一直转起来
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
- (void)timerMethod {
// 添加timer才开启block任务的执行,不然数组tasks会因为添加30个而导致数组中保存的仅仅是最后五个,TableView一出来cell图片没显示,因为就没有对应的任务
self.display = YES;
}
(2)通过当前定时器的线程的runloop, 监听runloop将进入休眠事件
// 创建观察者
// 拿到当前的runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
// 定义一个上下文
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
// 创建一个观察者
_defaulModeObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &Callback, &context);
CFRunLoopAddObserver(runloop, _defaulModeObserver, kCFRunLoopCommonModes);
CFRelease(runloop);
static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
SecondViewController *vc = (__bridge SecondViewController *)info;
//无任务 退出
if (vc.tasks.count == 0) {
return;
}
if (vc.display) {
//从数组中取出任务
RunloopBlock task = vc.tasks.firstObject;
if (task) {
task();
//执行完任务之后移除任务
[vc.tasks removeObjectAtIndex:0];
}
}
}
(3)绑定cell时,添加加载图片任务
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ImageCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierID forIndexPath:indexPath];
NSInteger row = indexPath.row;
cell.imageView1.image = nil;
cell.imageView2.image = nil;
cell.imageView3.image = nil;
//添加任务1
[self addTask:^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"%ld",(long)(row%3)]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView1.image = img;
});
});
}];
//添加任务2
[self addTask:^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"%ld",(long)(row%3)]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView2.image = img;
});
});
}];
//添加任务3
[self addTask:^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"%ld",(long)(row%3)]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView3.image = img;
});
});
}];
return cell;
}
添加任务方法:
- (void)addTask:(RunloopBlock)task {
if (self.display) {
// 判断当前待执行的代码块是否超出最大代码块数
if (self.tasks.count > self.maxQueueLength) {
// 干掉最开始的代码块
[self.tasks removeObjectAtIndex:0];
}
}
// 将代码块添加到可变数组中
[self.tasks addObject:task];
}
3.懒加载图片demo源代码下载
下载地址: https://gitee.com/rang/tableview_lazy-demo.git