tableView 是我们在开发项目中必不可少的控件,相对于一些列表或者是有规律的数据使用起来会实现数据展示快速有方便。那么项目中使用tableVIew我们是否真的做到相关的性能优化呢?本文是自我总结和学习的一个过程,很多资料都是开发加上网上借阅的,如果有更好的认识或者问题,各位大神指教。
tableView的优化在本人看来可以从两个方面去入手,一个是从tableView控件本身,二是利用系统运行循环机制去做相应的优化
一,从tableView的本身去优化,
tableView滑动为什么会卡顿
项目开放中我我们都知道 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
要比- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;先进行调用和计算,也即是说
cell赋值内容时,会根据内容设置布局,也就可以知道cell的高度,若有1000行,就会调用1000次 cellForRow方法,而我们对cell的处理操作,都是在这个方法中赋值,布局等等,开销很大。
UITableViewCell重用机制
UITableView只会创建一屏幕(或者一屏幕多一点)的cell,其他都是取出来重用的。每当cell滑出屏幕的时候,就会放到一个集合中,当要显示某一位置的cell时,会先去集合中取,有的话,就直接拿出来显示,没有在创建。
优化方法
heightForRow方法处理cellForRow各司其职高。
思路:赋值和计算布局分离。
cellForRow负责赋值,
heightRorRow负责计算高度。
自定义cell绘制
遇到比较复杂的界面的时候,如复杂点的图文混排,上面的那种优化行高的方式可能就不能满足要求了,当然了,由于我的开发经验尚短,说实话,还没遇到要将自定义的Cell重新绘制。至于这方面,大家可以参考标题
按需加载
开发的过程中,自定义Cell的种类千奇百怪,但Cell本来就是用来显示数据的,不说100%带有图片,也差不多,这个时候就要考虑,下滑的过程中可能会有点卡顿,尤其网络不好的时候,异步加载图片是个程序员都会想到,但是如果给每个循环对象都加上异步加载,开启的线程太多,一样会卡顿,我记得好像线程条数一般3-5条,最多也就6条吧。这个时候利用UIScrollViewDelegate两个代理方法就能很好地解决这个问题。
总结
1.提前计算并缓存好高度,因为heightForRow最频繁的调用。
2.异步绘制,遇到复杂界面,性能瓶颈时,可能是突破口
3.滑动时按需加载,这个在大量图片展示,网络加载时,很管用。(SDWebImage已经实现异步加载)。
4.重用cells。
5.如果cell内显示得内容来自web,使用异步加载,缓存结果请求。当cell中的部分View是非常独立的,并且不便于重用的,而且“体积”非常小,在内存可控的前提下,我们完全可以将这些view缓存起来。当然也是缓存在模型中。
6.少用或不用透明图层,使用不透明视图。对于不透明的View,设置opaque为YES,这样在绘制该View时,就不需要考虑被View覆盖的其他内容(尽量设置Cell的view为opaque,避免GPU对Cell下面的内容也进行绘制)
7.减少subViews。分析Cell结构,尽可能的将 相同内容的抽取到一种样式Cell中,前面已经提到了Cell的重用机制,这样就能保证UITbaleView要显示多少内容,真正创建出的Cell可能只比屏幕显示的Cell多一点。虽然Cell的’体积’可能会大点,但是因为Cell的数量不会很多,完全可以接受的
8.少用addView给cell动态添加view,可以初始化的时候就添加,然后通过hide控制是否显示。只定义一种Cell,那该如何显示不同类型的内容呢?答案就是,把所有不同类型的view都定义好,放在cell里面,通过hidden显示、隐藏,来显示不同类型的内容。毕竟,在用户快速滑动中,只是单纯的显示、隐藏subview比实时创建要快得多。
二,利用Runloop优化tableView
卡顿原因
tableView加载过多的高清大图,并且一个cell加载多张图片的时候,拖动tableView时,runloop不仅需要处理拖动事件,还要处理图片渲染,从而造成卡顿。
需求:
从网络加载高清大图到UITableViewCell上,而且每个Cell上面加载多张图片,当cell数量过多的时候,我们需要保持流畅度和加载速度。
runloop如何优化tableView
把任务以block块的方式封装起来,存放到任务数组中,若任务数组中的任务数超出最大任务数,则删除靠前的任务,注册runloop的观察者,在回调方法里,执行任务数组中的一个任务,并删除执行后的任务。添加timer事件,防止runloop进入休眠状态
实现思路
为了解决tableView的卡顿现象,可以runloop一次处理一个任务,
根据当前的runloop和观察者的上下文,通过CFRunLoopObserverCreate函数,定义一个观察runloop即将进入休眠状态(BeforeWaiting)时的观察者,添加runloop在common模式下的监听,
在观察者回调函数中,根据观察者上下文执行当前对象任务数组中的第一个任务,任务执行后从数组中移除,
在绘制cell的方法(即cellForRow)中,调用添加任务的方法,在这个方法的内部,将用block代码块包装的任务添加到数组中,若超出最大任务数,则删除之前的任务,便于观察者回调函数分开执行任务,减少对系统资源的消耗,
最后添加timer或source0 事件,使runloop不进入休眠状态。
实现的代码逻辑
1,定义一个存放执行任务的block
typedef void(^SaveFuncBlock)(void);//存放定时任务
//存放任务的数组
@property (nonatomic, strong) NSMutableArray *saveTaskMarr;
//最大任务数(超过最大任务数的任务就停止执行)
@property (nonatomic, assign) NSInteger maxTasksNumber;
//任务执行的代码块
@property (nonatomic, copy) SaveFuncBlock saveFuncBlock;
2,添加runloop
-(void)RunLoopOptimization
{
//1、先获取当前的Runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//定义观察者
CFRunLoopObserverRef observer;
CFRunLoopObserverContext context = {
0,
(__bridge void *)self,
&CFRetain,
&CFRelease,
NULL
};
//创建观察者 参数一:分配空间的方式,参数二:运行循环状态,参数三:是否一直监听,参数四:优先级 参数五:回调函数的地址,参数六:上下文
observer = CFRunLoopObserverCreate(kCFAllocatorMalloc, kCFRunLoopBeforeWaiting, YES, 0, &Callback, &context);
//添加观察者,添加在common模式下面
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
//创建定时器 (保证runloop回调函数一直在执行)
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(notDoSomething)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
3,通过match_port实现回调
//定义一个回调函数 一次RunLoop来一次
static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
MomentViewController * vcSelf = (__bridge MomentViewController *)(info);
if (vcSelf.saveTaskMarr.count > 0) {
//获取一次数组里面的任务并执行
SaveFuncBlock funcBlock = vcSelf.saveTaskMarr.firstObject;
funcBlock();
[vcSelf.saveTaskMarr removeObjectAtIndex:0];
}
}
4,添加任务
//添加任务进数组保存
-(void)addTasks:(SaveFuncBlock)taskBlock{
[self.saveTaskMarr addObject:taskBlock];
//超过每次最多执行的任务数就移出当前数组
if (self.saveTaskMarr.count > self.maxTasksNumber) {
[self.saveTaskMarr removeObjectAtIndex:0];
}
}
- (void)notDoSomething {
// 不做事情,就是为了让 callBack() 函数一直相应
}
网友评论