美文网首页iOS开发攻城狮的集散地UI自我学习材料
解决tableview加载高清图片,滑动卡顿问题

解决tableview加载高清图片,滑动卡顿问题

作者: pengmengli | 来源:发表于2017-07-26 12:09 被阅读446次

    产品今天给了个需求,最简单的tableview上展示数据,不过有个问题是给的图片都是高清的,所以滑动的时候不流畅,然后就去搜索,最后找到一个大神写的代码,通过runloop解决,感觉很不错,所以写篇文章记录一下。

    runloop介绍

    1,首先大家先了解一下runloop,网上一搜一大堆,我就简单说一下,大家都知道runloop的主要作用是不断的循环监听事件的,有事件发生时都会触发它,但是不同的事件触发它的模式不同(NSDefaultRunLoopMode和NSRunLoopCommonModes最常用的模式),时间计时器,网络请求会触发它的NSDefaultRunLoopMode,交互(点击,滑动)会触发NSRunLoopCommonModes,NSRunLoopCommonModes比NSDefaultRunLoopMode优先级更高,我们平常应该遇到过,当你滑动tableview的时候你的时间计时器就会停止,所以你就会把你的时间计时器切换到NSRunLoopCommonModes。(这里需要用代码提醒一下,把时间计时器切换到NSRunLoopCommonModes会带来哪些坏处)。

    - (void)viewDidLoad {

    [super viewDidLoad];

    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(test) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

    }

    - (void)test{

    [NSThread sleepForTimeInterval:1.0];

    NSLog(@"睡一秒钟");

    }

    我们可以看到在test的方法里面有个耗时操作,这时候当你滑动tableview的时候,就会卡顿,因为它们都处于NSRunLoopCommonModes模式,没有优先级之分了。所以用NSRunLoopCommonModes的时候要注意有没有耗时操作。

    2,runloop还有一个功能就是,每次循环都会对你界面上的ui绘制一遍,主要是速度快,我们看不出来,所以当界面中出现高清的图片时因为绘制的慢,就会导致卡顿。

    3,对tableview性能优化一般有:加载耗时操作放子线程,更新ui放主线程,说到这里,我们提一下,为什么更新ui放主线程,可能有些人只知道更新ui放主线程,不知道为什么,ui都是UIKit框架的东西,为了增加效率,苹果不建议定义它的(ui的一些类)属性的时候加线程锁的,我们都会用nonatomic,非原子性的,它不是线程安全的,所以把更新ui放到主线程,防止出现多个线程访问它,出现问题。

    操作不流畅的代码

    下面问题来了,也是我们今天要讲的,如果更新ui也是耗时操作,就像在2提到的,绘制高清图片,那咱们就先来一段卡顿的代码

    #import "ViewController.h"

    @interface ViewController ()

    @end

    @implementation ViewController

    - (void)viewDidLoad {

    [super viewDidLoad];

    UITableView *tb = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];

    tb.delegate = self;

    tb.dataSource = self;

    [self.view addSubview:tb];

    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    return 200;

    }

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

    return 85;

    }

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    NSString *identifier = @"cell";

    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:identifier];

    if(cell==nil){

    cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault      reuseIdentifier:identifier];

    }

    for (UIView *subView in cell.contentView.subviews) {

    [subView removeFromSuperview];

    }

    UILabel *contentLb = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 40)];

    contentLb.font = [UIFont systemFontOfSize:15];

    contentLb.textColor = [UIColor grayColor];

    contentLb.numberOfLines = 2;

    contentLb.lineBreakMode = NSLineBreakByClipping;

    contentLb.text = @"网络是由节点和连线构成,表示诸多对象及其相互联系。在数学上,网络是一种图,一般认为";

    [cell.contentView addSubview:contentLb];

    NSString *path = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"jpg"];

    UIImage *image = [UIImage imageWithContentsOfFile:path];

    UIImageView *iv1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, contentLb.frame.origin.y+contentLb.frame.size.height, 80, 40)];

    iv1.image = image;

    [cell.contentView addSubview:iv1];

    UIImageView *iv2 = [[UIImageView alloc] initWithFrame:CGRectMake(iv1.frame.origin.x+iv1.frame.size.width, contentLb.frame.origin.y+contentLb.frame.size.height, 80, 40)];

    iv2.image = image;

    [cell.contentView addSubview:iv2];

    UIImageView *iv3 = [[UIImageView alloc] initWithFrame:CGRectMake(iv2.frame.origin.x+iv2.frame.size.width, contentLb.frame.origin.y+contentLb.frame.size.height, 80, 40)];

    iv3.image = image;

    [cell.contentView addSubview:iv3];

    return cell;

    }

    paceship.jpg这张图片我用的差不多有3m,这时候会有稍微的不流畅,还不是那么明显,你可以用个更大的试试。那么我们怎么优化这段代码呢。

    优化之后代码

    1,因为我们拖拽tableview,runloop就要循环一次,对我们的ui进行绘制一遍,由于图片太大,绘制时间长,出现卡顿,上面我们说过,拖拽的时候runloop处于NSRunLoopCommonModes模式,如果在这个模式下我们不让他绘制图片,等他处于NSDefaultRunLoopMode(拖拽完后他就会回到NSDefaultRunLoopMode模式),我们再让它绘制图片是不是就不会卡顿了,这时候我们就要对runloop进行监听了。给它添加观察者,下面看代码实现

    #import "ViewController.h"

    #import <objc/runtime.h>

    //定义一个block,用来存放加载图片的事件typedef BOOL(^RunloopBlock)(void);

    @interface ViewController ()

    /** 定义一个数组,存放加载图片的事件 */

    @property (nonatomic,strong) NSMutableArray *tasks;

    /** 最大任务数,因为我们一个屏幕可能就显示20张图片,这个数不确定,看你自己设置cell的高度了,如果你一个cell上放2个图片,一个屏幕上就能看到2个cell,那你的最大数就是4 */

    @property(assign,nonatomic)NSUInteger max;

    /**添加一个timer,可以一直让runloop处于唤醒状态,并且处于NSDefaultRunLoopMode模式,这样就可以不断绘制图片*/

    @property(nonatomic,strong)NSTimer * timer;

    @end

    @implementation ViewController

    - (void)_timerFiredMethod{

    }

    - (void)viewDidLoad {

    [super viewDidLoad];

    _max = 28;//我设置的cell高度,一个屏幕能显示28张图片

    _tasks = [NSMutableArray array];

    _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(_timerFiredMethod) userInfo:nil repeats:YES];

    UITableView *tb = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];

    tb.delegate = self;

    tb.dataSource = self;

    [self.view addSubview:tb];

    //注册监听

    [self addRunloopObserver];

    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    return 200;

    }

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

    return 85;

    }

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    NSString *identifier = @"cell";

    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:identifier];

    if(cell==nil){

    cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault      reuseIdentifier:identifier];

    }

    for (UIView *subView in cell.contentView.subviews) {

    [subView removeFromSuperview];

    }

    UILabel *contentLb = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 40)];

    contentLb.font = [UIFont systemFontOfSize:15];

    contentLb.textColor = [UIColor grayColor];

    contentLb.numberOfLines = 2;

    contentLb.lineBreakMode = NSLineBreakByClipping;

    contentLb.text = @"网络是由节点和连线构成,表示诸多对象及其相互联系。在数学上,网络是一种图,一般认为";

    [cell.contentView addSubview:contentLb];

    //不要直接加载图片!! 你将加载图片的代码!都给RunLoop!!

    [self addTask:^BOOL{

    NSString *path = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"jpg"];

    UIImage *image = [UIImage imageWithContentsOfFile:path];

    UIImageView *iv1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, contentLb.frame.origin.y+contentLb.frame.size.height, 80, 40)];

    iv1.image = image;

    [cell.contentView addSubview:iv1];

    return YES;

    }];

    [self addTask:^BOOL{

    NSString *path = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"jpg"];

    UIImage *image = [UIImage imageWithContentsOfFile:path];

    UIImageView *iv2 = [[UIImageView alloc] initWithFrame:CGRectMake(80, contentLb.frame.origin.y+contentLb.frame.size.height, 80, 40)];

    iv2.image = image;

    [cell.contentView addSubview:iv2];

    return YES;

    }];

    [self addTask:^BOOL{

    NSString *path = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"jpg"];

    UIImage *image = [UIImage imageWithContentsOfFile:path];

    UIImageView *iv3 = [[UIImageView alloc] initWithFrame:CGRectMake(160, contentLb.frame.origin.y+contentLb.frame.size.height, 80, 40)];

    iv3.image = image;

    [cell.contentView addSubview:iv3];

    return YES;

    }];

    return cell;

    }

    //添加任务

    - (void)addTask:(RunloopBlock)unit{

    [self.tasks addObject:unit];

    //保证之前没有显示出来的任务,不再浪费时间加载

    if (self.tasks.count > self.max) {

    [self.tasks removeObjectAtIndex:0];

    }

    }

    //添加监听,用来监听runloop

    - (void)addRunloopObserver{

    //获取当前的RunLoop

    CFRunLoopRef runloop = CFRunLoopGetCurrent();//用CFRunLoopGetCurrent()和CFRunLoopGetMain()都一样,因为我们现在操作都是在主线程,这个方法就是得到主线程的runloop,因为每个线程都有一个runloop

    //定义一个观察者,这是一个结构体

    CFRunLoopObserverContext context = {

    0,

    (__bridge void *)(self),

    &CFRetain,

    &CFRelease,

    NULL

    };

    //定义一个观察者

    static CFRunLoopObserverRef defaultModeObsever;

    //创建观察者

    defaultModeObsever = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, NSIntegerMax - 999, &Callback, &context);//kCFRunLoopBeforeWaiting观察runloop等待的时候就是处于NSDefaultRunLoopMode模式的时候,YES是否重复观察,Callback回掉方法,就是处于NSDefaultRunLoopMode时候要执行的方法,其他参数我也不知道什么意思

    //添加当前RunLoop的观察者

    CFRunLoopAddObserver(runloop, defaultModeObsever, kCFRunLoopDefaultMode);

    //c语言有creat 就需要release

    CFRelease(defaultModeObsever);

    }

    static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){

    ViewController * vc = (__bridge ViewController *)(info); //这个info就是我们在context里面放的self参数

    if (vc.tasks.count == 0) {

    return;

    }

    BOOL result = NO;

    while (result == NO && vc.tasks.count) {

    //取出任务

    RunloopBlock unit = vc.tasks.firstObject;

    //执行任务

    result = unit();

    //干掉第一个任务

    [vc.tasks removeObjectAtIndex:0];

    }

    }

    @end

    这是改装后的代码

    相关文章

      网友评论

        本文标题:解决tableview加载高清图片,滑动卡顿问题

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