iOS图片加载策略的简单实现

转载 2018年01月04日 00:00:00

点击上方“iOS开发”,选择“置顶公众号”

关键时刻,第一时间送达!

640?wxfrom=5&wx_lazy=1

0.gif?wxfrom=5&wx_lazy=1


今天和大家一起来讨论如何进行iOS图片加载策略的简单实现,有疏忽的地方,还望各位不吝赐教。


一、不自量力的说明


对于iOS图片加载策略的实现,相信大家和我一样更多的还是借助于第三方,我在此班门弄斧的意义是应付一些公司的面试,尝试以一种简单的方式去实现图片加载策略,借此也说明一些其他方面的知识。在此我将使用一个TableView的例子进行说明,采用的是MVC的设计模式。如果采用的是Swift编写,还请给位大神自行转换。


二、逻辑叙述


这个逻辑的流程是我随手画的,只做参考。


?wx_fmt=png

逻辑实现.png


三、实现过程 -- 以多图下载为例


1、先上一个简单粗暴的实现。


    // cellForRowAtIndexPath:方法中的实现过程

    // 1、设置cell的重用标识

    static NSString *cellID = @"app";

    // 2、创建cell

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];

    // 3、设置cell的数据 AppItem是模型

    AppItem *item = self.apps[indexPath.row];

    // 4、设置标题

    cell.textLabel.text = item.name;

    // 5、设置子标题

    cell.detailTextLabel.text = item.download;

    // 6、设置图标

    NSURL *url = [NSURL URLWithString:item.icon];

    NSData *imageData = [NSData dataWithContentsOfURL:url];

    UIImage *image = [UIImage imageWithData:imageData];

    cell.imageView.image = image;

    NSLog(@"%zd----",indexPath.row);


2、以上的方式实现的问题


  • 图片重复下载,通过添加的打印可以很明显的看出来,解决方式是把之前下载好的图片保存起来,因为图片和文字要对应,所以采用字典的方式进行存储。


1、添加内存缓存解决图片重复下载问题


    // 针对图片重复下载的问题 

    // 1、创建一个NSMutableDictionary属性来保存图片

    /** 内存缓存 */

    @property (nonatomic, strong) NSMutableDictionary *images;

     // 2、懒加载实现

    - (NSMutableDictionary *)images{

        if (!_images) {

            _images = [NSMutableDictionary dictionary];

        }

        return _images;

    }

    // 3、设置图标改造

    // 先去查看内存缓存中该图片有没有被下载过,如果有直接拿来用,如果没有再下载

    // 设置图片,否则直接下载

    UIImage *image = [self.images objectForKey:item.icon];

    // 如果有值直接拿来用

    if(image){

        cell.imageView.image = image;

        NSLog(@"使用了内存缓存中的图片%zd----",indexPath.row);

    }else{

        NSURL *url = [NSURL URLWithString:item.icon];

        NSData *imageData = [NSData dataWithContentsOfURL:url];

        UIImage *image = [UIImage imageWithData:imageData];

        cell.imageView.image = image;

        // 保存图片到内存缓存 用图片的URL作为图片的key 保证key唯一

        [self.images setObject:image forKey:item.icon];

        NSLog(@"下载了图片%zd----",indexPath.row);

    }


2、添加内存缓存存在问题,需要用磁盘缓存(沙盒缓存)来补充


   /*

    * 两种情况 图片没有下载和应用关闭都要考虑 

    * 只是添加内存缓存在程序退出的时候内存缓存会被释放,接着优化

    */

    /*  沙盒缓存的相关概念

     document :会备份,苹果官方不允许将缓存放到这里,上架被拒绝

     library:

        preference:偏好设置,存放账号密码等数据

        cache: 保存缓存文件,不会被备份

     temp:临时路径(随时有可能被删除)

     */

    // 先去查看内存缓存中该图片有没有被下载过,如果有直接拿来用,如果没有就去检查磁盘缓存,如果没有磁盘缓存,就保存一份到内存,设置图片,否则就直接下载

    UIImage *image = [self.images objectForKey:item.icon];

    // 如果有值直接拿来用

    if(image){


        cell.imageView.image = image;

        NSLog(@"使用了内存缓存中的图片----%zd",indexPath.row);


    }else{

        

        // 沙盒缓存路径获取(磁盘缓存)

        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

        // 获得图片的名称

        NSString *imageName = [item.icon lastPathComponent];

        // 拼接全路径

        NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

        // 检查磁盘缓存

        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

        if (imageData) {

            // 设置图标

            UIImage *image = [UIImage imageWithData:imageData];

            cell.imageView.image = image;

            NSLog(@"沙盒存储----%zd",indexPath.row);

            // 保存图片到内存缓存

            [self.images setObject:image forKey:item.icon];  


        }else{

            

            NSURL *url = [NSURL URLWithString:item.icon];

            NSData *imageData = [NSData dataWithContentsOfURL:url];

            UIImage *image = [UIImage imageWithData:imageData];

            cell.imageView.image = image;

            // 保存图片到内存缓存 用图片的URL作为图片的key 保证key唯一

            [self.images setObject:image forKey:item.icon];

            // 保存图片到沙盒缓存(磁盘缓存)

            [imageData writeToFile:fullPath atomically:YES];

            NSLog(@"下载了图片----%zd",indexPath.row);


        }  


    }


  • UI不流畅,因为下载图片操作和刷新UI的操作都是在主线程中操作的,解决方法把下载操作放到子线程中进行操作。


   /*

    * 改造下载图片的部分,下载图片放到子线程中去做,刷新UI放在主线程中做。

    */

    /** 并发队列  使用NSOperation为了防止重复创建队列,设置全局属性*/

    @property (nonatomic, strong) NSOperationQueue *queue;

    // 并发队列懒加载

    - (NSOperationQueue *)queue{

        if (!_queue) {

            _queue = [[NSOperationQueue alloc] init];

            // 设置最大并发数

            _queue.maxConcurrentOperationCount = 5;

        }

    

        return _queue;

    }

    // 设置图标的位置修改如下:

    // 先去查看内存缓存中该图片有没有被下载过,如果有直接拿来用,如果没有就去检查磁盘缓存,如果没有磁盘缓存,就保存一份到内存,设置图片,否则就直接下载。

    UIImage *image = [self.images objectForKey:item.icon];

    // 如果有值直接拿来直接使用

    if(image){


        cell.imageView.image = image;

        NSLog(@"使用了内存缓存中的图片----%zd",indexPath.row);


    }else{

        

        // 沙盒缓存路径获取(磁盘缓存)

        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

        // 获得图片的名称

        NSString *imageName = [item.icon lastPathComponent];

        // 拼接全路径

        NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

        // 检查磁盘缓存

        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

        if (imageData) {

            // 设置图标

            UIImage *image = [UIImage imageWithData:imageData];

            cell.imageView.image = image;

            NSLog(@"沙盒存储----%zd",indexPath.row);

            // 保存图片到内存缓存

            [self.images setObject:image forKey:item.icon];  


        }else{

            // 创建操作

            NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{

                NSURL *url = [NSURL URLWithString:item.icon];

                NSData *imageData = [NSData dataWithContentsOfURL:url];

                UIImage *image = [UIImage imageWithData:imageData];

                NSLog(@"下载------%@",[NSThread currentThread]);

                [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                    cell.imageView.image = image;

                    NSLog(@"UI------%@",[NSThread currentThread]);

                }];

                // 保存图片到内存缓存 用图片的URL作为图片的key 保证key唯一

                [self.images setObject:image forKey:item.icon];

                // 保存图片到沙盒缓存(磁盘缓存)

                [imageData writeToFile:fullPath atomically:YES];

                 NSLog(@"下载了图片%zd----",indexPath.row);

            }];

            // 添加下载操作到并发队列中

            [self.queue addOperation:blockOperation];


        }  

    }


3、进行了2以后产生的新问题


  • UI不会自动刷新,当我拖动的时候才会刷新页面,解决方式要使用代码手动刷新。因为下载操作是异步的,会先把没有图标的cell返回,此时因为图标的frame为0,所以之后即使图片下载下来,frame变为其他额尺寸,frame还是会一直为0,所以不会显示。如果我手动刷新,系统会从新走一遍cellForRowAtIndexPath:方法,到设置图标这一步时,会直接把内存中存在的图片(此时图片是有frame的)直接设置到cell中会显示。


   /*

    * 改造下载图片的部分,下载图片放到子线程中去做,刷新UI放在主线程中做。

    */

    // 设置图标的位置修改如下:

    // 先去查看内存缓存中该图片有没有被下载过,如果有直接拿来用,如果没有就去检查磁盘缓存,如果没有磁盘缓存,就保存一份到内存,设置图片,否则就直接下载。

    UIImage *image = [self.images objectForKey:item.icon];

    // 如果有值直接拿来直接使用

    if(image){


        cell.imageView.image = image;

        NSLog(@"使用了内存缓存中的图片----%zd",indexPath.row);


    }else{

        

        // 沙盒缓存路径获取(磁盘缓存)

        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

        // 获得图片的名称

        NSString *imageName = [item.icon lastPathComponent];

        // 拼接全路径

        NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

        // 检查磁盘缓存

        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

        if (imageData) {

            // 设置图标

            UIImage *image = [UIImage imageWithData:imageData];

            cell.imageView.image = image;

            NSLog(@"沙盒存储----%zd",indexPath.row);

            // 保存图片到内存缓存

            [self.images setObject:image forKey:item.icon];  


        }else{

            // 创建下载操作

            NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{

                NSURL *url = [NSURL URLWithString:item.icon];

                NSData *imageData = [NSData dataWithContentsOfURL:url];

                UIImage *image = [UIImage imageWithData:imageData];

                NSLog(@"下载------%@",[NSThread currentThread]);

                [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                    cell.imageView.image = image;

                    NSLog(@"UI------%@",[NSThread currentThread]);

                    // 手动刷新 刷新UITableView指定的行

                    [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

                }];

                // 保存图片到内存缓存 用图片的URL作为图片的key 保证key唯一

                [self.images setObject:image forKey:item.icon];

                // 保存图片到沙盒缓存(磁盘缓存)

                [imageData writeToFile:fullPath atomically:YES];

                 NSLog(@"下载了图片%zd----",indexPath.row);

            }];

            // 添加下载操作到并发队列中

            [self.queue addOperation:blockOperation];


        }  

    }


  • 由于拖动太快,导致的重复下载的问题。解决方式是定义一个操作缓存(字典),把之前的操作都保存起来,因为上面出现的原因本质就是因为blockOperation重复添加到queue中了。这个问题是这样的,需要显示的图片会进行下载操作,但是一张图片下载需要时间,如果图片还没下载下来,我就拖动了,将它移出屏幕,结果就是这张图片没有下载完,如果这时候我又拖动了,又要显示这张图片,因为上一次还没下载完,所以内存和磁盘里都没有,所以会重复下载。解决方式是定义一个操作缓存(字典),把之前的操作都保存起来,因为上面出现的原因本质就是因为blockOperation重复添加到queue中了。

 

   /** 定义操作缓存属性 */

    @property (nonatomic, strong) NSMutableDictionary *operations;

    // 操作缓存属性懒加载实现

    - (NSMutableDictionary *)operations{


        if (!_operations) {

            _operations = [NSMutableDictionary dictionary];

        }

    

        return _operations;

    }

    // 设置图标的位置修改如下:

    // 先去查看内存缓存中该图片有没有被下载过,如果有直接拿来用,如果没有就去检查磁盘缓存,如果没有磁盘缓存,就保存一份到内存,设置图片,否则就直接下载。

    UIImage *image = [self.images objectForKey:item.icon];

    // 如果有值直接拿来直接使用

    if(image){


        cell.imageView.image = image;

        NSLog(@"使用了内存缓存中的图片----%zd",indexPath.row);


    }else{

        

        // 沙盒缓存路径获取(磁盘缓存)

        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

        // 获得图片的名称

        NSString *imageName = [item.icon lastPathComponent];

        // 拼接全路径

        NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

        // 检查磁盘缓存

        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

        if (imageData) {

            // 设置图标

            UIImage *image = [UIImage imageWithData:imageData];

            cell.imageView.image = image;

            NSLog(@"沙盒存储----%zd",indexPath.row);

            // 保存图片到内存缓存

            [self.images setObject:image forKey:item.icon];  


             }else{


                // 检查图片是否在操作缓存中进行下载,如果在下载就什么也不做,如果不在就添加下载任务

                NSBlockOperation *blockOperation = [self.operations objectForKey:item.icon];

                if (blockOperation) {

                

                }else{

                

                    // 创建下载操作

                    blockOperation = [NSBlockOperation blockOperationWithBlock:^{

                    NSURL *url = [NSURL URLWithString:item.icon];

                    NSData *imageData = [NSData dataWithContentsOfURL:url];

                    UIImage *image = [UIImage imageWithData:imageData];

                    NSLog(@"下载------%@",[NSThread currentThread]);

                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                        cell.imageView.image = image;

                        NSLog(@"UI------%@",[NSThread currentThread]);

                        // 手动刷新 刷新UITableView指定的行

                        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

                    }];

                    // 保存图片到内存缓存 用图片的URL作为图片的key 保证key唯一

                    [self.images setObject:image forKey:item.icon];

                     // 保存图片到沙盒缓存(磁盘缓存)

                     [imageData writeToFile:fullPath atomically:YES];

                    // 下载操作完成后进行移除操作

                    [self.operations removeObjectForKey:item.icon];

                     }];

                    // 添加下载操作到操作缓存中

                    [self.operations setObject:blockOperation forKey:item.icon];

                    // 添加下载操作到并发队列中

                    [self.queue addOperation:blockOperation];

            

        }   

    } 

}


  • cell的重用机制导致的cell图标展示数据错乱的问题,解决方案,如果要进行下载图片,先清空原来cell上的图片,但是一般不会直接设置为nil,会采用占位图片的方式来解决。


    /** 定义操作缓存属性 */

    @property (nonatomic, strong) NSMutableDictionary *operations;

    // 操作缓存属性懒加载实现

    - (NSMutableDictionary *)operations{


        if (!_operations) {

            _operations = [NSMutableDictionary dictionary];

        }

    

        return _operations;

    }

    // 设置图标的位置修改如下:

    // 先去查看内存缓存中该图片有没有被下载过,如果有直接拿来用,如果没有就去检查磁盘缓存,如果没有磁盘缓存,就保存一份到内存,设置图片,否则就直接下载。

    UIImage *image = [self.images objectForKey:item.icon];

    // 如果有值直接拿来直接使用

    if(image){


        cell.imageView.image = image;

        NSLog(@"使用了内存缓存中的图片----%zd",indexPath.row);


    }else{

        

        // 沙盒缓存路径获取(磁盘缓存)

        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

        // 获得图片的名称

        NSString *imageName = [item.icon lastPathComponent];

        // 拼接全路径

        NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];

        // 检查磁盘缓存

        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

        if (imageData) {

            // 设置图标

            UIImage *image = [UIImage imageWithData:imageData];

            cell.imageView.image = image;

            NSLog(@"沙盒存储----%zd",indexPath.row);

            // 保存图片到内存缓存

            [self.images setObject:image forKey:item.icon];  


             }else{


                // 检查图片是否在操作缓存中进行下载,如果在下载就什么也不做,如果不在就添加下载任务

                NSBlockOperation *blockOperation = [self.operations objectForKey:item.icon];

                if (blockOperation) {

                

                }else{


                    // 防止cell重用导致的数据错乱 先设置cell 的 image为空

                    cell.imageView.image = [UIImage imageNamed:@"placeHolder.png"];

                    // 创建下载操作

                    blockOperation = [NSBlockOperation blockOperationWithBlock:^{

                    NSURL *url = [NSURL URLWithString:item.icon];

                    NSData *imageData = [NSData dataWithContentsOfURL:url];

                    UIImage *image = [UIImage imageWithData:imageData];

                    NSLog(@"下载------%@",[NSThread currentThread]);

                    // 当url地址不正确 image为空 容错处理

                    if (!image) {

                        // 为了下一次进来的时候再次尝试进行图片下载

                        [self.operations removeObjectForKey:item.icon];

                        return ;

                    }

                      [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                            cell.imageView.image = image;

                            NSLog(@"UI------%@",[NSThread currentThread]);

                            // 手动刷新 刷新UITableView指定的行

                            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

                        }];

                    // 保存图片到内存缓存 用图片的URL作为图片的key 保证key唯一

                    [self.images setObject:image forKey:item.icon];

                     // 保存图片到沙盒缓存(磁盘缓存)

                     [imageData writeToFile:fullPath atomically:YES];

                     // 下载操作完成后进行移除操作

                    [self.operations removeObjectForKey:item.icon];

                     }];

                    // 添加下载操作到操作缓存中

                    [self.operations setObject:blockOperation forKey:item.icon];

                    // 添加下载操作到并发队列中

                    [self.queue addOperation:blockOperation];

            

        }   

    } 

}


内存问题:将图片保存在内存中是很方便的事,图片少的情况下肯定没问题,但是图片多了就会内存警告,要做一下处理。


- (void)didReceiveMemoryWarning{

    // 移除内存缓存,这里不会影响界面显示,因为有强引用的关系。

    [self.images removeAllObjects];

    // 移除队列中所有操作

    [self.queue cancelAllOperations];

}


写在最后的话:关于iOS图片加载策略的知识今天就分享到这里,关于iOS图片加载策略实现方面的问题欢迎大家和我交流,共同进步,谢谢各位。


640?

  • 来自: 听海听心

  • 链接:https://www.jianshu.com/p/6edf6b9b2c1b

  • iOS开发整理发布,转载请联系作者授权

0.gif

0?【点击成为Android大神】

Android之微信图片加载

MainActivityimport java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import j...
  • King1425
  • King1425
  • 2016年11月05日 17:03
  • 596

iOS 正确选择图片加载方式

ios内存稀缺,而图片资源通常又是最占内存的部分之一,因此,选择如何加载图片,对于优化应用内存占用量,能起到立竿见影的效果。通常加载图片的方式有两种:...
  • Apple_app
  • Apple_app
  • 2014年08月26日 14:08
  • 12672

关于ios异步加载图片的三个开源项目

原文链接:http://prolove10.blog.163.com/blog/static/138411843201211231213598/ 一、HjCache  原文...
  • lengshengren
  • lengshengren
  • 2013年10月22日 17:01
  • 3880

您玩过这十三款iOS策略游戏了吗?

子曰:三人行,必有我师焉,择其善者而从之,其不善者而改之。是故,乃成此篇。 一、小小战争(Great Little War Game) “Great Little War Game”是一款采用...
  • cping1982
  • cping1982
  • 2011年11月20日 22:19
  • 25446

iOS 图片加载 圆形进度条

项目中有加载网络图片的需求,加一个加载的进度条会提高用户体验,网络不好的时候会清晰的看到图片加载的进度,比让用户看着满屏幕空白好。下面是我们项目自己封装的圆形进度条,分享给大家。 其实实现原理很简单...
  • July_sal
  • July_sal
  • 2015年05月18日 12:21
  • 2441

iOS 数据库升级策略

  • ternenceWei
  • ternenceWei
  • 2016年01月04日 20:14
  • 453

Android加载大图的优化策略

当我们使用大的Bitmap图片时很容易出现OOM的现象,今天我们就来看下该怎么解决这个问题。 一般有两种方法: 1、压缩图片; 2、LruCache缓存; 当然这两种方式同时使用效果更好^^ ...
  • wdong_love_cl
  • wdong_love_cl
  • 2016年06月04日 14:18
  • 4062

iOS性能优化---转载《一》iOS 异步图片加载优化与常用开源库分析

1. 网络图片显示大体步骤: 下载图片图片处理(裁剪,边框等)写入磁盘从磁盘读取数据到内核缓冲区从内核缓冲区复制到用户空间(内存级别拷贝)解压缩为位图(耗cpu较高)如果位图数据不是字节对齐的,...
  • shaobo8910
  • shaobo8910
  • 2016年01月21日 15:56
  • 830

ios uitableview加载图片优化

举个例子,当我们在用网易新闻App时,看着那么多的新闻,并不是所有的都是我们感兴趣的,有的时候我们只是很快的滑过,想要快速的略过不喜欢的内容,但是只要滑动经过了,图片就开始加载了,这样用户体验就不太好...
  • jks456
  • jks456
  • 2015年04月20日 10:33
  • 1023

ios 图片加载不出

晚上就仅仅一个png图片加载不了,最后发现是图片的问题,虽然后缀是png,但是其实不是png的图片。 解决办法:              参考:png的图片只是命名为png,格式不是png的。 ...
  • robinson_911
  • robinson_911
  • 2017年08月17日 23:20
  • 225
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:iOS图片加载策略的简单实现
举报原因:
原因补充:

(最多只允许输入30个字)