iOS性能优化---tableView懒加载图片

作者: 火之玉 | 来源:发表于2017-07-07 16:31 被阅读2108次

    关于tableView的性能优化问题一直都是iOS开发者必备的一项优化技能, 可以好不夸张的说, app里面半数以上的页面都需要用到tableView; 对于它的优化网友也总结的非常详细, 鄙人就不详细解释了, 附个链接:
    关于tableview的优化

    本文主要介绍一下如何在tableView中实现懒加载图片, 别怪我多嘴, 一些初学者对懒加载的印象可能是这样的:

    @interface ViewController()
    @property (nonatomic,strong) NSMutableArray *lazyLoadArr;
    @end
    @implementation ViewController
    - (NSMutableArray *)lazyLoadArr
    {
        if (!_lazyLoadArr) {
            _lazyLoadArr = [NSMutableArray array];
        }
        return _lazyLoadArr;
    }
    @end
    

    实例中定义了一个懒加载的可变数组, 重写数组的getter方法, 在该方法中判断是否存在lazyLoadArr, 如果存在就返回, 不存在就初始化, 这样保证数组只加载了一次; 而这个只是懒加载中比较常见的一种用法;
    那么什么是懒加载呢?

    懒加载也叫延迟加载, 或者通俗来讲就是用到时再去加载对象

    也就是说用到这种思想的基本上都属于懒加载;


    其实在tableView懒加载图片也是app中比较常见的功能; 它可以可以减轻服务器的压力, 节约流量, 提高页面的加载速度等等, 费这么大劲儿, 最终也是为了更好的用户体验;
    先附上实现效果图, 让大家感受一下:

    效果图.gif
    这个例子节选自苹果的官方例子LazyTableImages实现图片懒加载;
    LazyTableImages下载

    OK, 现在我们开始剖析一下代码;
    附上工程目录:

    工程目录.png

    首先说一下细节部分, 其实也不是什么大问题, 留意一下:
    ①工程里面的AppDelegate类被替换成LazyTableAppDelegate, 其实在函数入口main.m替换下类就行;
    ②pch文件导入了Foundation, UIKit框架全局调用, 所以出现了以下情况:

    AppRecord.h

    一般对对象的基础操作都放在了Foundation.h, 为了方便, 通常也就直接引入<Foundation/Foundation.h>;

    数据请求

    为了减少数据延迟加载的卡顿感, demo里直接把数据请求写在了LazyTableAppDelegate文件中; 选一小段代码说一下:

                __weak ParseOperation *weakParser = self.parser;
                self.parser.completionBlock = ^(void) {
                    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    if (weakParser.appRecordList != nil)
                    {
                        dispatch_async(dispatch_get_main_queue(), ^{    
                            RootViewController *rootViewController =
                                (RootViewController*)[(UINavigationController*)weakSelf.window.rootViewController topViewController];
                            rootViewController.entries = weakParser.appRecordList;
                            [rootViewController.tableView reloadData];
                        });
                    }
                    weakSelf.queue = nil;
                };     
                [self.queue addOperation:self.parser];
    
    • block循环引用问题, 这个经常遇到, 就不多说了;
    • 数据请求是在子线程里进行了, 然而要想对UI进行操作, 需要回到主线程, 刷新数据;
    • 对于networkActivityIndicatorVisible, 这个是UIApplication的一个属性, 用于在状态栏上显示菊花转的图标,表明网络状态, 而我们平常都是用MB或者SVP来表示网络加载的, 有可能没见过, 说明一下;

    UI部分(tableView)

    看了示意图我们发现只有在tableView松开的时候才加载图片, 那这个是怎么实现的呢, 我从tableView的cellForRowAtIndexPath:代理方法中选了一段代码:

             if (!appRecord.appIcon)
                {
                    if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
                    {
                        [self startIconDownload:appRecord forIndexPath:indexPath];
                    }
                    cell.imageView.image = [UIImage imageNamed:@"Placeholder.png"];                
                }
                else
                {
                   cell.imageView.image = appRecord.appIcon;
                }
    

    当没有下载好的图片, 即!appRecord.appIcon时, 判断是否停止拖拽(self.tableView.dragging == NO)或者加速度为0(self.tableView.decelerating == NO), 都满足的话就下载图片, 这样就实现了图示效果;
    但是光这样处理是不够的, 如果连续拖动, 不让tableView停止, 里面的图片是空的, 所以还要进行下面的操作:

    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
    {
        if (!decelerate)
        {
            [self loadImagesForOnscreenRows];
        }
    }
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
        [self loadImagesForOnscreenRows];
    }
    

    由于tableView继承了scrollView, 所以才在draggingdecelerating结束的时候加载图片, 最终实现了图示效果;

    优化亮点

    一. 图片圆角优化

    一般在开发中我们是这样给图片切圆角的:

          myImage.layer.cornerRadius = 50;
          myImage.layer.masksToBounds = YES;
    

    其中masksToBounds(CALayer)表示视图的图层上的子图层,如果超出父图层的部分就截取掉;还有clipsToBounds(UIView),是指视图上的子视图,如果超出父视图的部分就截取掉。
    在iOS9.0之前这样设置会触发离屏渲染,比较消耗性能。尤其是tableView中(处理了大量图片),会明显的感到页面有轻微卡顿。
    那要怎么处理呢, 一般我们用Core Graphics绘制圆角, 附上工程里的代码:

          CGSize itemSize = CGSizeMake(kAppIconSize, kAppIconSize);
          UIGraphicsBeginImageContextWithOptions(itemSize, NO, 0.0f);
          CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
          [image drawInRect:imageRect];
          self.appRecord.appIcon = UIGraphicsGetImageFromCurrentImageContext();
          UIGraphicsEndImageContext();
    
    二. model的小技巧

    我们发现在工程中, 无论对于tableView来说, 还是下载图片来说, 都是对同一个对象进行操作, demo中把tableView数据里面的对象扩展了下面这两条属性:

    @property (nonatomic, strong) UIImage *appIcon;
    @property (nonatomic, strong) NSString *imageURLString;
    

    这样我们去下载图片的时候, 只需要传进去一个AppRecord类的对象, 返回来也是同一个对象:

         // 将下载完成的网络图片的data值传给这个对象
        UIImage *image = [[UIImage alloc] initWithData:data];
        self.appRecord.appIcon = image;
    

    有人要问了只是简化了传入的数据嘛, 没什么特别的, 但是你细看tableView里面cellForRowAtIndexPath方法就会发现, 这样做既简化了逻辑判断, 又易于理解, 因为是同一个对象;

        AppRecord *appRecord = (self.entries)[indexPath.row];
          if (!appRecord.appIcon)         //  简便的判断条件
                {
                    if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
                    {
                        [self startIconDownload:appRecord forIndexPath:indexPath];
                    }
                    cell.imageView.image = [UIImage imageNamed:@"Placeholder.png"];                
                }
                else
                {
                   cell.imageView.image = appRecord.appIcon;
                }
    

    这个思想真是不由得让老铁双击666啊, 非常棒👍;

    好的, 这次就分享那么多, 以后还得研究更多更深的源码, 加油⛽️;

    相关文章

      网友评论

      • 阶前梧叶:model里持有「@property (nonatomic, strong) UIImage *appIcon;」图片,如果数据很多,图片又大,内存不就会太大,从而导致内存警告,app被杀死。。。。
        O小豪O:原图在使用后就会被销毁,只是保存了指定大小的图片,怎么会出现内存溢出的情况呢?就算你想保存的图片和屏幕一样大小,你也很难让内存溢出(基本上相对于原图还是小很多的)
        火之玉:@阶前梧叶 这个提醒的很好,可以考虑只存下图片存储的路径🤗
      • PGOne爱吃饺子:和sdWebimage结合使用一下会不会更好呢,毕竟sd在性能方面也做得不错的
        火之玉: @4140d18ee6fc 好的呀,本来就打算读的
        PGOne爱吃饺子:@火之玉 大哥 阅读完之后可以写个关于sd的分享么,尤其是缓存这一块的,么么哒
        火之玉:是的呢, 之后估计会阅读一下sd, (๑•̀ㅂ•́)و✧加油
      • Evyn_:请问有demo嘛?
        火之玉: @Evyn_ 么事
        Evyn_:@火之玉 谢谢啦!
        火之玉:这个就是苹果官方的demo, 文中有一个"LazyTableImages下载", 点击下载即可
      • 梁森的简书:有用到runloop来优化tableview吗?
        火之玉:@夕阳下de奔跑 是条思路,但感觉最好还是用路径吧,就像图片过大最好用imageWithContentsOfFile一样🤗
        阳光下de奔跑:将图片储存到内存中 会导致内存过大的问题, 是不是可以将图片转为二进制 再转md5呢
        火之玉:我没用到过, 不过看网友用runloop来优化tableView滑动的流畅度, 可以看看这篇文章http://www.cnblogs.com/yjg2014/p/5153225.html

      本文标题:iOS性能优化---tableView懒加载图片

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