NSTimer在tableView中的复用问题

之前,有位朋友,在写商品倒计时是,因为定时器在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:

点击打开链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值