UITableView重用机制和原理
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
滑动过程中,A3,A4,A5全部显示在屏幕上,A1移除屏幕时,就被放到重用池中,当A7即将显示在屏幕上时,从重用池中根据指定的≈取出可重用的cell,
如果A1-A7为同一种identifier标识符的话,A7就可以复用A1所创建的cell的内存或者控件,就达到了cell复用的机制
实现一个tableView的索引表的重用池 (Demo百度网盘)
默认一加载是6个索引,当点击页面上一个按钮时,变成10个索引,在此点击变回6个索引,依次循环
1.按上图自定义tableview,添加变量containerView和reusePool,其中containerView添加在tableView的最上方, reusePool继承NSObject,用它来实现复用池
2.复用池用两个NSMutableSet集合来实现,一个为usingQueue,一个为waitUsedQueue
3.在tableView的reloadData方法里面(这个方法在数据源一开始会调用一次,后面手动调用),懒加载初始化reusePool,首先按照图一重置两个队列
然后通过代理,从VC中获取索引数组(6或者10)
循环生成数组个数的button添加到contentview上显示,这一步用的是复用池
4.复用池是,每次循环,都按照图二,首先从等待队列中取button,如果取到了,将它移动到使用队列中并使用,若没取到,创建一个button并将它添加到使用队列中使用
5.每次刷新,重走3和4,则实现了一直复用的效果
备注:图三即为,初始是索引为6,全部为新建的,点击按钮reloadData,索引为11
则有6个是复用的,剩下的为新建的,此时队列中有11个button
再点击按钮reloadData,索引为6,全部复用
.....
下面两图为每次reloadDate时复用池的工作过程
一
二
三
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 实现重用机制的类
@interface ViewReusePool : NSObject
// 从重用池当中取出一个可重用的view
- (UIView *)dequeueReusableView;
// 向重用池当中添加一个视图
- (void)addUsingView:(UIView *)view;
// 重置方法,将当前使用中的视图移动到可重用队列当中
- (void)reset;
@end
#import "ViewReusePool.h"
@interface ViewReusePool ()
// 等待使用的队列
@property (nonatomic, strong) NSMutableSet *waitUsedQueue;
// 使用中的队列
@property (nonatomic, strong) NSMutableSet *usingQueue;
@end
@implementation ViewReusePool
- (id)init{
self = [super init];
if (self) {
_waitUsedQueue = [NSMutableSet set];
_usingQueue = [NSMutableSet set];
}
return self;
}
// 从重用池当中取出一个可重用的view
- (UIView *)dequeueReusableView{
UIView *view = [_waitUsedQueue anyObject];
if (view == nil) {
return nil;
}
else{
// 进行队列移动
[_waitUsedQueue removeObject:view];
[_usingQueue addObject:view];
return view;
}
}
// 向重用池当中添加一个视图
- (void)addUsingView:(UIView *)view
{
if (view == nil) {
return;
}
// 添加视图到使用中的队列
[_usingQueue addObject:view];
}
// 重置方法,将当前使用中的视图移动到可重用队列当中
- (void)reset{
UIView *view = nil;
while ((view = [_usingQueue anyObject])) {
// 从使用中队列移除
[_usingQueue removeObject:view];
// 加入等待使用的队列
[_waitUsedQueue addObject:view];
}
}
@end
每次reloadData时
// 标记所有视图为可重用状态
[reusePool reset];
//
NSUInteger count = arrayTitles.count;
CGFloat buttonWidth = 60;
CGFloat buttonHeight = self.frame.size.height / count;
for (int i = 0; i < [arrayTitles count]; i++) {
NSString *title = [arrayTitles objectAtIndex:i];
// 从重用池当中取一个Button出来
UIButton *button = (UIButton *)[reusePool dequeueReusableView];
// 如果没有可重用的Button重新创建一个
if (button == nil) {
button = [[UIButton alloc] initWithFrame:CGRectZero];
button.backgroundColor = [UIColor whiteColor];
// 注册button到重用池当中
[reusePool addUsingView:button];
NSLog(@"新创建一个Button");
}
else{
NSLog(@"Button 重用了");
}
// 添加button到父视图控件
[containerView addSubview:button];
[button setTitle:title forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// 设置button的坐标
[button setFrame:CGRectMake(0, i * buttonHeight, buttonWidth, buttonHeight)];
}
reloadData
reloadData是异步绘制的
[tableView reloadData]并不会等待tableview更新结束后才执行后续代码
而是立即执行后续代码,然后异步地去计算tableView的高度,获取cell等等
如果表中的数据非常大,在一个run loop周期没执行完,
这时就显示tableView视图数据的操作就会出问题了。
解决方法是:
1. 通过layoutIfNeeded方法,强制重绘并等待完成。
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
//刷新完成,执行后续需要执行的代码
2.reloadData方法会在主线程执行,通过GCD,使后续操作排队在reloadData后面执行。
[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
//刷新完成,执行后续代码
});
数据源同步
我们在刷新数据时会面临下面的问题,某些用户交互的操作是在主线程进行的,而网络和数据解析是在子线程进行的,当主线程做了例如删除行A的操作并刷新了UI后,若之后子线程数据返回,因为子线程拿到的是删除前的数据拷贝,当它处理完数据后,返回到主线程刷新UI,因为此时子线程是有行A的,但主线程是没有行A的,刷新后主线程就会又出现行A,此时就会出现数据问题
方案一:并发访问,数据拷贝,会增加内存开销
在主线程删除时记录下来,之后在子线程返回数据将要更新UI时,同步一下记录的删除操作,也就是在子线程中也删除,再回到主线程就是同步的
网友评论