老实说,UITableView性能优化 这个话题,最常常遇到的仍是在面试中,常见的回答例如:ios
最近遇到一个需求,对tableView
有中级优化需求git
tableView
滚动的时候,滚动到哪行,哪行的图片才加载并显示,滚动过程当中图片不加载显示;以最多见的cell加载webImage为例:github
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
DemoModel *model = self.datas[indexPath.row];
cell.textLabel.text = model.text;
[cell.imageView setYy_imageURL:[NSURL URLWithString:model.user.avatar_large]];
return cell;
}
复制代码
cell
没进入到界面中(还不可见),不会调用- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
去渲染cell,在cell中若是设置loadImage
,不会调用;cell
进去界面中的时候,再进行cell渲染(不管是init仍是从复用池中取) YYCache
会对图片进行数据缓存,以key
:value
的形式,这里的key = imageUrl
,value = 下载的image图片
YYCache
中是否有该url,有的话,直接读取缓存图片数据,没有的话,走图片下载逻辑,并缓存图片如上设置,若是咱们cell一行有20行,页面启动的时候,直接滑动到最底部,20个cell都进入过了界面,- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
被调用了20次,不符合 需求1
的要求web
解决办法:面试
cell
每次被渲染时,判断当前tableView
是否处于滚动状态,是的话,不加载图片;cell
滚动结束的时候,获取当前界面内可见的全部cell
2
的基础之上,让全部的cell
请求图片数据,并显示出来- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
DemoModel *model = self.datas[indexPath.row];
cell.textLabel.text = model.text;
//不在直接让cell.imageView loadYYWebImage
if (model.iconImage) {
cell.imageView.image = model.iconImage;
}else{
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
//核心判断:tableView非滚动状态下,才进行图片下载并渲染
if (!tableView.dragging && !tableView.decelerating) {
//下载图片数据 - 并缓存
[ImageDownload loadImageWithModel:model success:^{
//主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = model.iconImage;
});
}];
}
}
复制代码
- (void)p_loadImage{
//拿到界面内-全部的cell的indexpath
NSArray *visableCellIndexPaths = self.tableView.indexPathsForVisibleRows;
for (NSIndexPath *indexPath in visableCellIndexPaths) {
DemoModel *model = self.datas[indexPath.row];
if (model.iconImage) {
continue;
}
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[ImageDownload loadImageWithModel:model success:^{
//主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = model.iconImage;
});
}];
}
}
复制代码
//手一直在拖拽控件
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
[self p_loadImage];
}
//手放开了-使用惯性-产生的动画效果
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if(!decelerate){
//直接中止-无动画
[self p_loadImage];
}else{
//有惯性的-会走`scrollViewDidEndDecelerating`方法,这里不用设置
}
}
复制代码
dragging
:returns YES if user has started scrolling. this may require some time and or distance to move to initiate dragging
能够理解为,用户在拖拽当前视图滚动(手一直拉着)缓存
deceleratingreturns
:returns YES if user isn't dragging (touch up) but scroll view is still moving
能够理解为用户手已放开,试图是否还在滚动(是否惯性效果)性能优化
当前代码生效的效果以下: bash
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
DemoModel *model = self.datas[indexPath.row];
cell.textLabel.text = model.text;
if (model.iconImage) {
cell.imageView.image = model.iconImage;
}else{
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
/**
runloop - 滚动时候 - trackingMode,
- 默认状况 - defaultRunLoopMode
==> 滚动的时候,进入`trackingMode`,defaultMode下的任务会暂停
中止滚动的时候 - 进入`defaultMode` - 继续执行`trackingMode`下的任务 - 例如这里的loadImage
*/
[self performSelector:@selector(p_loadImgeWithIndexPath:)
withObject:indexPath
afterDelay:0.0
inModes:@[NSDefaultRunLoopMode]];
}
//下载图片,并渲染到cell上显示
- (void)p_loadImgeWithIndexPath:(NSIndexPath *)indexPath{
DemoModel *model = self.datas[indexPath.row];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[ImageDownload loadImageWithModel:model success:^{
//主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = model.iconImage;
});
}];
}
复制代码
效果与demo.gif
的效果一致服务器
runloop - 两种经常使用模式介绍:
trackingMode
&&defaultRunLoopMode
app
- 默认状况 - defaultRunLoopMode
- 滚动时候 - trackingMode
- 滚动的时候,进入
trackingMode
,致使defaultMode
下的任务会被暂停,中止滚动的时候 ==> 进入defaultMode
- 继续执行defaultMode
下的任务 - 例如这里的defaultMode
大tips:这里,若是使用RunLoop,滚动的时候虽然不执行
defaultMode
,可是滚动一结束,以前cell中的p_loadImgeWithIndexPath
就会所有再被调用,致使相似YYWebImage
的效果,其实也是不知足需求,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//p_loadImgeWithIndexPath一进入`NSDefaultRunLoopMode`就会执行
[self performSelector:@selector(p_loadImgeWithIndexPath:)
withObject:indexPath
afterDelay:0.0
inModes:@[NSDefaultRunLoopMode]];
}
复制代码
效果如上
- 滚动的时候不加载图片,滚动结束加载图片-知足
- 滚动结束,以前滚动过程当中的
cell
会加载图片 => 不知足需求
git reset --hard runloop以前
解决: 需求2. 页面跳转的时候,取消当前页面的图片加载请求;
- (void)p_loadImgeWithIndexPath:(NSIndexPath *)indexPath{
DemoModel *model = self.datas[indexPath.row];
//保存当前正在下载的操做
ImageDownload *manager = self.imageLoadDic[indexPath];
if (!manager) {
manager = [ImageDownload new];
//开始加载-保存到当前下载操做字典中
[self.imageLoadDic setObject:manager forKey:indexPath];
}
[manager loadImageWithModel:model success:^{
//主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = model.iconImage;
});
//加载成功-从保存的当前下载操做字典中移除
[self.imageLoadDic removeObjectForKey:indexPath];
}];
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
NSArray *loadImageManagers = [self.imageLoadDic allValues];
//当前图片下载操做所有取消
[loadImageManagers makeObjectsPerformSelector:@selector(cancelLoadImage)];
}
@implementation ImageDownload
- (void)cancelLoadImage{
[_task cancel];
}
@end
复制代码
思路:
indexPath
:manager
的格式,将当前的图片下载操做存起来viewWillDisappear
的时候,取出当前线程字典中的全部线程对象,遍历进行cancel
操做,完成需求最近网上各类互联网公司裁人信息铺天盖地,甚至包括各类一线公司 ( X东 X乎 都扛不住了吗-。-)iOS原本就是提早进入寒冬,iOS小白们能够尝试思考下这个问题
答:
YYWebImage
为例,能够先下载图片,再对图片进行圆角处理,再设置到cell
上显示答: 若是是下载完,在回调中进行切割圆角的处理,其实缓存的图片是原图,等于每次取的时候,缓存中取出来的都是矩形图片,每次set
都得作切割操做;
答:实际上是有的,简单来讲YYWebImage
能够拆分红两部分,默认状况下,咱们拿到的回调,是走了 download
&& cache
的流程了,这里咱们多作一步,取出cache
中该url
路径对应的图片,进行圆角切割,再存储到 cache中,就能保证之后每次拿到的就都是cacha
中已经裁切好的圆角图片
详情可见:
NSString *path = [[UIApplication sharedApplication].cachesPath stringByAppendingPathComponent:@"weibo.avatar"];
YYImageCache *cache = [[YYImageCache alloc] initWithPath:path];
manager = [[YYWebImageManager alloc] initWithCache:cache queue:[YYWebImageManager sharedManager].queue];
manager.sharedTransformBlock = ^(UIImage *image, NSURL *url) {
if (!image) return image;
return [image imageByRoundCornerRadius:100]; // a large value
};
复制代码
SDWebImage
同理,它有暴露了一个方法出来,能够直接设置保存图片到磁盘中,无需修改源码
“winner is coming”,若是面试正好遇到以上问题的,请叫我雷锋~ 衷心但愿各位iOS小伙伴门能熬过这个冬天?
参考资料