美文网首页iOS复习
UITableView深度优化

UITableView深度优化

作者: 花椒不麻牙 | 来源:发表于2017-03-16 14:13 被阅读324次

    UITableView的核心思想是:cell的重用机制。UITbleView只会创建一屏幕(或一屏幕多一点)的cell, 每当cell滑出屏幕时,就会放倒一个集合(或数组)中(这里相当于一个重用池),当要显示某一个位置的cell时,会先根据ReuseIdentifier去集合和数组中去取,如果有直接拿来用,如果没有的话,才会去创建,这样极大地减少了内存的开销。

    UITableView的两个重要的回调方法是:tableView:cellForRowAtIndexPath:和tableView:heightForRowAtIndexPath:。由于UITableView是继承自UIScrollView的,需要先确定它的contentSize及每个Cell的位置,然后才会把重用的cell放置到对应的位置。UITableView的回调顺序是先多次调用tableView:heightForRowAtIndexPath:来确定cell的位置,然后才会调用tableView:cellForRowAtIndexPath:从而来显示在当前的屏幕的cell。

    举个例子来说:如果现在要显示100个Cell,当前屏幕显示5个。那么刷新(reload)UITableView时,UITableView会先调用100次tableView:heightForRowAtIndexPath:方法,然后调用5次tableView:cellForRowAtIndexPath:方法;滚动屏幕时,每当Cell滚入屏幕,都会调用一次tableView:heightForRowAtIndexPath:、tableView:cellForRowAtIndexPath:方法。

    1:把赋值和计算布局分离。这样让tableView:cellForRowAtIndexPath:只负责赋值,tableView:heightForRowAtIndexPath:只负责计算高度。注意,这两个方法各司其职。

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    NSDictionary *dict = self.dataList[indexPath.row];

    return[ContacterTableCell cellHeightOfInfo:dict];

    }

    基于上面的思路,我们可以在获得数据后,直接根据数据源计算出对应的布局,并缓存到数据源中,这样在tableView:heightForRowAtIndexPath:方法中就直接返回高度,而不需要每次都计算了。

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    NSDictionary *dict = self.dataList[indexPath.row];

    CGRect rect = [dict[@"frame"] CGRectValue];

    returnrect.frame.height;

    }

    2:上面的方案并不是最佳的方案,可以满足简单的界面!如果像朋友圈那样的图文混排,这种方案其实扛不住的,自定义cell的绘制。在cell上添加系统的控件时,实质系统都需要调用底层的接口进行绘制,如果大量的添加,对资源的开销会很大,所以我们可以直接绘制,提高效率。

    首先自定义cell的draw方法,(也可以重写drawRect)然后在方法中实现

    //异步绘制

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    CGRect rect = [_data[@"frame"] CGRectValue];

    UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);//<CoreGraphics/CoreGraphics.h>

    CGContextRef context = UIGraphicsGetCurrentContext();

    //整个内容的背景

    [[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];

    CGContextFillRect(context, rect);

    //转发内容的背景

    if([_data valueForKey:@"subData"]) {

    [[UIColor colorWithRed:243/255.0 green:243/255.0 blue:243/255.0 alpha:1] set];

    CGRect subFrame = [_data[@"subData"][@"frame"] CGRectValue];

    CGContextFillRect(context, subFrame);

    [[UIColor colorWithRed:200/255.0 green:200/255.0 blue:200/255.0 alpha:1] set];

    CGContextFillRect(context, CGRectMake(0, subFrame.origin.y, rect.size.width, .5));

    }

    {

    //名字

    float leftX = SIZE_GAP_LEFT+SIZE_AVATAR+SIZE_GAP_BIG;

    float x = leftX;

    float y = (SIZE_AVATAR-(SIZE_FONT_NAME+SIZE_FONT_SUBTITLE+6))/2-2+SIZE_GAP_TOP+SIZE_GAP_SMALL-5;

    [_data[@"name"] drawInContext:context withPosition:CGPointMake(x, y) andFont:FontWithSize(SIZE_FONT_NAME)

    andTextColor:[UIColor colorWithRed:106/255.0 green:140/255.0 blue:181/255.0 alpha:1]

    andHeight:rect.size.height];

    //时间+设备

    y += SIZE_FONT_NAME+5;

    float fromX = leftX;

    float size = [UIScreen screenWidth]-leftX;

    NSString *from = [NSString stringWithFormat:@"%@  %@", _data[@"time"], _data[@"from"]];

    [from drawInContext:context withPosition:CGPointMake(fromX, y) andFont:FontWithSize(SIZE_FONT_SUBTITLE)

    andTextColor:[UIColor colorWithRed:178/255.0 green:178/255.0 blue:178/255.0 alpha:1]

    andHeight:rect.size.height andWidth:size];

    }

    //将绘制的内容以图片的形式返回,并调主线程显示

    UIImage *temp = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    dispatch_async(dispatch_get_main_queue(), ^{

    if(flag==drawColorFlag) {

    postBGView.frame = rect;

    postBGView.image = nil;

    postBGView.image = temp;

    }

    }

    //内容如果是图文混排,就添加View,用CoreText绘制

    [self drawText];

    }}

    大体的思路是这个情况,各个信息都是根据之前算好的布局进行绘制的。这里是需要异步绘制,但如果在重写 drawRect方法就不需要用GCD异步线程了,因为drawRext本来就是异步绘制的。

    3:进行异步绘制这样的话,UITableView的效率提高了一个等级!不过我们还可以从UIScrollerView的角度出发,再次找到突破口

    滑动UITableView时按需加载对应的内容:

    //按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。

    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{

    NSIndexPath *ip = [self indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];

    NSIndexPath *cip = [[self indexPathsForVisibleRows] firstObject];

    NSInteger skipCount = 8;

    if(labs(cip.row-ip.row)>skipCount) {

    NSArray *temp = [self indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.width, self.height)];

    NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];

    if(velocity.y<0) {

    NSIndexPath *indexPath = [temp lastObject];

    if(indexPath.row+33) {

    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];

    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];

    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];

    }

    }

    [needLoadArr addObjectsFromArray:arr];

    }

    }

    记得在tableView:cellForRowAtIndexPath:方法中加入判断:

    if(needLoadArr.count>0&&[needLoadArr indexOfObject:indexPath]==NSNotFound) {

    [cell clear];

    return;

    }

    滚动很快时,只加载目标范围内的Cell,这样按需加载,极大的提高流畅度。

    总结:UITableView的优化主要从三个方面入手

    (1)提前计算并缓存好高度,因为heightForRowAtIndexPath:是调用最频繁的方法

    (2)异步绘制,遇到复杂的界面,遇到性能瓶颈时,可能就是突破点

    (3)滑动时按需加载,这个在大量的图片展示,网络加载的时候很管用!

    除了以上最主要的三个方面,还有其他的优化点:

    •正确使用reuseIdentifier来重用cells

    •尽量使所有的view opaque,包括cell自身。

    opaque属性提示绘制系统如何处理view。如果opaque设置为YES,绘图系统会将view看为完全不透明,这样会绘图系统就可以优化一些绘制操作以提升性能。如果设置为NO,那么绘图系统结合其它的内容来处理view。默认情况下,这个属性是YES.

    •尽量少用或不用透明图层

    •如果cell内的显示的内容来自web,使用异步加载,缓存请求结果

    •减少subviews的数量

    •在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果

    •尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示

    相关文章

      网友评论

        本文标题:UITableView深度优化

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