美文网首页
UI视图-UITableView重用池机制及优化

UI视图-UITableView重用池机制及优化

作者: 亲爱的大倩倩 | 来源:发表于2019-07-09 11:30 被阅读0次
    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时,同步一下记录的删除操作,也就是在子线程中也删除,再回到主线程就是同步的


    方案二:串行访问,若子线程的数据解析很慢,那么主线程的删除操作就会有延迟

    相关文章

      网友评论

          本文标题:UI视图-UITableView重用池机制及优化

          本文链接:https://www.haomeiwen.com/subject/inqahctx.html