之前,有位朋友,在写商品倒计时是,因为定时器在tableview上总是被复用,使改界面拖延了很久,最好找我写了个demo才解决。之后,还有不少iOS的朋友遇到类似复用的问题。这里只讲我这个demo中的关键代码及对复用的理解,demo的下载链接在文章最后。(如有不足请提出)
首先讲讲tableview的复用:cell在创建的时候,只会创建设备屏幕可见的cell个数,而不是按照你的数据源中的数据个数来创建的。然后反复使用这些cell,这样可以占用少量的内存来达到需要的效果。
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString * ID = @"3829472938";
TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
[cell setDataWithTitle:_array[indexPath.row]];
return cell;
}
这段代码,相信大家再熟悉不过了,但是不是所有人都理解这段代码。
static NSString * ID = @"3829472938";
TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
可以理解成为创建的cell一个标记
if (!cell) {
cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
是加入cell为空,就创建新的cell,同时给占用这个内存的cell一个标记,然后再给cell赋值。而最重要的就是赋值这一步。因为,当你在拖动tableview的时候,加入该创建的cell都创建完了,那么就需要通过原有的cell来为需要的新的cell来赋值,通过赋值来改变cell上需要显示的数据,很多复用的问题就是因为没有及时的更新对应的数据。特别是在对待一些动态数据上,很多人在赋值一次以后,没有再次更新数据源,于是复用时,就重新赋上了数据源中的初始数据,造成显示数据不正确或者复用的问题。
下面是demo的核心代码:
控制器中定时器的创建
//创建定时器,控制数据源中的倒计时在controller中与cell的分类中一致
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//子线程创建定时器
[NSThread detachNewThreadSelector:@selector(multiThread) toTarget:self withObject:nil];
}
-(void)multiThread{
//确认定时器不是在主线程上时,创建定时器
//如果定时器要在子线程上跑起来,需要添加runloop
if (![NSThread isMainThread]) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countdown:) userInfo:nil repeats:YES];
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSRunLoopCommonModes];
[runloop run];
}
}
主要用来更新数据源对应的数据
-(void)countdown:(NSTimer *)timer{
//遍历数据源,如果对应下标的元素是NSNumber类型,就-1
for (int i = 0; i < _array.count; i++) {
id obj = [_array objectAtIndex:i];
if ([obj isKindOfClass:[NSNumber class]]) {
int a = [obj intValue];
a -- ;
if (a <= -1) {
[_array replaceObjectAtIndex:i withObject:@"被点击"];
}else{
[_array replaceObjectAtIndex:i withObject:@(a)];
}
}
}
}
自定义cell中button的定时器
//按钮点击事件
-(void)clicked:(UIButton *)button{
// button.selected = YES;
if (_countDown==0) {
_countDown = 20;
}
[_delegate takeTimeToControllerWithTime:_countDown Button:button];
//子线程创建定时器
[NSThread detachNewThreadSelector:@selector(multiThread) toTarget:self withObject:nil];
}
-(void)setDataWithTitle:(id)title{
//赋值时判断传进来的参数类型,如果是模型或字典就没必要这样判断了
if ([title isKindOfClass:[NSNumber class]]) {
_countDown = [title intValue];
// [self clicked:_button];
if (_countDown==0) {
_countDown = 5;//自己改成120
}
//子线程创建定时器
[NSThread detachNewThreadSelector:@selector(multiThread) toTarget:self withObject:nil];
}else{
if (_timer.valid) {
//这里一定要判断,当title是字符串的时候,如果定时器还开着,一定要关,防止内存泄露和线程卡死,只要销毁定时器,runloop就会从线程上休眠
[self stop];
}
[_button setTitle:title forState:UIControlStateNormal];
}
}
-(void)multiThread{
//确认定时器不是在主线程上时,创建定时器
//如果定时器要在子线程上跑起来,需要添加runloop
if (![NSThread isMainThread]) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countdown:) userInfo:nil repeats:YES];
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSRunLoopCommonModes];
[runloop run];
}
}
//定时器倒计时
-(void)countdown:(id)send{
[_button setTitle:[NSString stringWithFormat:@"%d", _countDown] forState:UIControlStateNormal];
_countDown--;
if (_countDown <= -1) {
//当倒计时结束时,改变按钮标题
[_timer invalidate];
[_button setTitle:@"已点击" forState:UIControlStateNormal];
}
}
然后是最关键的一部,将点击的cell上的button改变的值存储到数据源中
//自定义cell的代理方法
-(void)takeTimeToControllerWithTime:(int)time Button:(UIButton *)button{
//获取点击后的cell的下标,改变对应的数据源中的数据
TableViewCell *selectedCell = (TableViewCell *)button.superview.superview;
NSIndexPath *index = [_tableView indexPathForCell:selectedCell];
[_array replaceObjectAtIndex:index.row withObject:@(time)];
}
详细代码请参考下面的demo: