YYKit的微博实例记录

查看YYKit的微博实例,学习tableView如何优化:

一.预排版

  1. 获取到json——》后台线程对每个cell数据封装为一个布局对象 CellLayout,缓存到内存,以供稍后使用:
    1)封装为model:WBTimeLineItem
    2)计算布局:WBStatueLayout;属性里面有model、布局结果

TableView 在请求各个高度函数时,不会消耗任何多余计算量;当把 CellLayout 设置到 Cell 内部时,Cell 内部也不用再计算布局了。

  1. 其他
    cell里面的每部分view都进行封装;并和cell放在一个类文件里面;
    model里面的每个小model,也和大model放在一个类文件里面;
    布局的layout文件,也是大类文件里面包含小的;

cell上面布局如下:


cell布局

二.预渲染:

1.圆角头像:

YYKit的demo里面是把头像下载后在后台线程渲染为圆型后,放到ImageCache缓存中。

对于 TableView 来说,Cell 内容的离屏渲染会带来较大的 GPU 消耗。尽量避免使用 layer 的 border、corner、shadow、mask 等技术,而尽量在后台线程预先绘制好对应内容。

-方法1: controller里面削圆具体操作如下(自己写的):

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImage *image = [UIImage imageNamed:@"11_1684_773"];
  
    UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    imageView.image = [self getImage:image];//削圆

    [self.view addSubview:imageView];
    
}
//削圆
-(UIImage *)getImage:(UIImage *)image{
    
    //1、开启上下文
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);

    //2、设置裁剪区域(rect:圆的x,y,直径)
    UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 512, 512)];
    [path addClip];

    //3、绘制图片
    [image drawAtPoint:CGPointZero];

    //4、获取新图片
    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();

    //5、关闭上下文
    UIGraphicsEndImageContext();

    //6、返回新图片
    return newImage;
    
}
  • 方法2:使用UIImage的分类操作如下(YYKit微博demo里面这么写):
.h:
@interface UIImage (TT)
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius;
@end

.m:
#import "UIImage+TT.h"
@implementation UIImage (TT)

- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius {
    return [self imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
}

- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
                          borderWidth:(CGFloat)borderWidth
                          borderColor:(UIColor *)borderColor {
    return [self imageByRoundCornerRadius:radius
                                  corners:UIRectCornerAllCorners
                              borderWidth:borderWidth
                              borderColor:borderColor
                           borderLineJoin:kCGLineJoinMiter];
}
//把图片预渲染为圆形
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
                              corners:(UIRectCorner)corners
                          borderWidth:(CGFloat)borderWidth
                          borderColor:(UIColor *)borderColor
                       borderLineJoin:(CGLineJoin)borderLineJoin {
    
    if (corners != UIRectCornerAllCorners) {
        UIRectCorner tmp = 0;
        if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
        if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
        if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
        if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
        corners = tmp;
    }
    
    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    
    CGContextScaleCTM(context, 1, -1);// 缩放
    CGContextTranslateCTM(context, 0, -rect.size.height);//平移
    
    CGFloat minSize = MIN(self.size.width, self.size.height);
    if (borderWidth < minSize / 2.0) {//削圆
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, 0, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];

        [path closePath];
        
        CGContextSaveGState(context);
        [path addClip];
        CGContextDrawImage(context, rect, self.CGImage);
        CGContextRestoreGState(context);
        
        
    }
    
    if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
        CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
        CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
        CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
        [path closePath];

        path.lineWidth = borderWidth;
        path.lineJoinStyle = borderLineJoin;
        [borderColor setStroke];
        [path stroke];
    }
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

@end
viewcontroller里面:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImage *image = [UIImage imageNamed:@"11_1684_773"];    
    UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    imageView.image =  [image imageByRoundCornerRadius:image.size.width/2.0];
    [self.view addSubview:imageView];
    
}
效果

2.其他削圆方法

1.最常见的方法:

    UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    imageView.image =  [UIImage imageNamed:@"11_1684_773"];
    //削圆
    imageView.layer.masksToBounds = YES;
    imageView.layer.cornerRadius = 50;
    
    [self.view addSubview:imageView];

这种对图片进行削圆会产生离屏渲染。如果不是tableView上面进行削圆,削圆的视图不是很多,直接这么操作即可。

2.使用CAShapeLayer和UIBezierPath设置圆角

    //1.创建削圆的曲线
    UIBezierPath * maskPath = [UIBezierPath bezierPathWithOvalInRect:self.imageView.bounds];
    
    //2.创建shaperLayer
    CAShapeLayer * maskLayer = [[CAShapeLayer alloc] init];
    //设置shaperLayer大小
    maskLayer.frame = self.imageView.bounds;
    //把曲线设置为shaperLayer的削圆曲线
    maskLayer.path = maskPath.CGPath;
    
    //3.把shaperLayer设置为imageView.layer.mask
    self.imageView.layer.mask = maskLayer;
    self.imageView.image = [UIImage imageNamed:@"1.png"];

使用的是设置蒙层的方法,其实也会产生离屏渲染。

异步绘制

下面这几部分还没有学会,道行太浅,需要把每个部分细致的学习后再回来慢慢咀嚼其中的好处;

demo里面把显示文本的控件上用到了异步绘制的功能;

全局并发控制

大量的任务提交到后台队列时,某些任务会因为某些原因(此处是 CGFont 锁)被锁住导致线程休眠,或者被阻塞,concurrent queue 随后会创建新的线程来执行其他任务。

这样App 在同一时刻就会存在几十个线程同时运行、创建、销毁。这些操作仍然会挤占掉主线程的 CPU 资源。

YYKit作者的 YYDispatchQueuePool,为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询返回其中一个 queue。
把 App 内所有异步操作,包括图像解码、对象释放、异步绘制等,都按优先级不同放入了全局的 serial queue 中执行,这样尽量避免了过多线程导致的性能问题。

更高效的异步图片加载

显示简单的单张图片时,利用 UIView.layer.contents就足够;使用 UIImageView 反而会带来额外的资源消耗;
YYKite作者实现了一个性能更高的图片加载库;在 CALayer 上添加了 setImageWithURL 等方法。

其他学习到的点:

  • 有不少视觉元素并不需要触摸事件,这些元素可以用 ASDK(第三方) 的图层合成技术预先绘制为一张图
  • 减少每个 Cell 内图层的数量,用 CALayer 替换掉 UIView
  • 把 Cell 按类型划分,进一步减少 Cell 内不必要的视图对象和操作

后续还需要学习:

  • 简单的 FPS 指示器:FPSLabel

  • 在后台线程操作对图片削圆,然后放到ImageCache缓存中

  • 使用CADisplayLink显示FPS
    -为了达到最高性能,你可能需要牺牲一些开发速度,不要用 Autolayout 等技术,少用 UILabel 等文本控件,所以还需要学习 layer的使用

  • Quartz 2D

  • YYKit的学习:YYLayout、YYDispatchQueuePool
    、里面对图片的加载和解码的使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,613评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,144评论 1 32
  • 静静的躺着 我望着天花板,望着灯光 发呆、沉默 静静的想着 我看着手机,胡乱地翻着 感伤、难言 静静的 我静静的 ...
    破钟阅读 288评论 0 0
  • 一位大学生暑假到云南参加社会实践活动。联欢时要表演节目,性格内向的他一时慌了手脚,说了这么段话:“我这个人从小就崇...
    24K超超老师阅读 320评论 0 0
  • 文/初漪 丁香 丁香, 丁香花开了 江南, 你不懂 你那里的花开花落 总是太快 二十一点,下课 约好寻找五瓣花 穿...
    初漪阅读 980评论 0 6