利用内存结构及多线程优化多图片下载(IOS篇)

时间:2022-12-10 00:19:34

利用内存结构及多线程优化多图片下载(IOS篇)

前言

下载地址, 后续发布, 请继续关注本blog

在IOS中,我们常常遇到多图片下载的问题。最简单的解决方案是直接利用别人写好的框架。但是这如同练武,只练外功而不练内功。
在这些框架中,SDWebImage这个框架是比较常用的框架,对于该框架的使用,不在这再做详细介绍。主要从计算机的视角和多线程
引发的一些问题来分享下如何自己做,或者说SDWebImage大体上也是基于这种方式来做的。在这之前,有必要先说下一些操作系统的基本架构和原理。

内存结构

其实在操作系统中,所谓的内存结构,不是指我们电脑中的内存。在专业术语中,电脑中的内存称为主存。而内存结构指的是由磁盘+主存+缓存
构成的结构。在这个构架中,从磁盘的速度比主存的速度慢,而主存的速度又比缓存的速度慢。这三种存储物质也是由不同的材料所做成,
所以缓存的价格大于主存的价格,而主存又大于磁盘的价格。要不然你都可以把电脑磁盘替换成内存了,那将是十分的快,当然的保证你电脑是不断电的。所以程序启动的时候
,都是从磁盘中读取数据,到主存中完成整个程序的加载,这时候,程序就在主存中。

重点

同样的道理,我们在做App的时候,对于图片下载这种问题。我们深知,必须得使用多线程来下载图片,然后另外一个线程来刷新界面。这才不会导致因为下载事件过长而引起的界面十分不流畅。同时,我们为了避免重复的下载图片,为用户节省流量,并且也为了提高图片的加载速度。我们有必要利用内存结构的特点来解决这个问题。所以对于这种问题,我们主要的思路就是
1.将下载的图片缓存到主存中开辟的一块缓存图片的空间。进行UI渲染的时候到缓存中取到对应的图片,渲染UI界面。

判断逻辑过程如下:

1.先判断主存缓存中有没有图片,如果没有进行第二步

2.判断磁盘有没有缓存的图片,如果有将其加载进内存,并缓存到主存中的缓存图片的位置。如果没有进行第三步

3.从网络中下载图片,并缓存到内存和磁盘上。

4.应用程序需用用到图片的时候,直接从内存中的缓存图片的位置拿。

存在的问题:

A.第一个是需要用子线程来下载图片,主线程进行渲染, 从而提高程序流畅性。

B.第二个是解决因为图片过大或者图片数据下载过慢时候,图片还没有下载完,还没缓存到内存中时候。用户不断拖拽TableView,

由于UITableViewCell循环利用,使得在进行判断1的时候,重复下载图片。

C.第三个是将主存中的图片缓存写入磁盘在渲染UI之前,但是我们可以为期在开个线程让两个同时进行,提高程序的效率。

对于第一个问题,很好解决。请看代码, 这是自定义cell中针对传入模型数据进行的处理。只需关注该重点,想要测试程序自行
到我的github上下载,如果你觉得这个程序对你有学习价值,记得给个star。

因为不想重复的粘贴代码,所以以下代码是最终版本的核心代码,但是为了说明问题。问题重现,所以请跟着我的步骤来,一步步的
打开被注释的代码,观察效果。
现在请你忽略所有注释的代码,先搞懂这是为了解决问题A。

- (void)setApp:(SWPApp *)app {

    _app = app;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 1.子线程下载数据

        SWPCache * cache = [SWPCache sharedInstance];

        // 1.1内存无缓存
// if ( cache.imageCache[app.icon] == nil ) { NSString * folderPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString * filePath = [folderPath stringByAppendingPathComponent:[app.icon lastPathComponent]]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath: filePath]; if (!fileExists) [self loadImageWithURLOrFilePath:app.icon isFilePath: NO]; // 1.2 磁盘无缓存则从网络下载
else [self loadImageWithURLOrFilePath:filePath isFilePath: YES]; // 1.3 磁盘有缓存, 直接加载进内存中的缓存 // } // [NSThread sleepForTimeInterval: 1]; // 2.主线程渲染cell的UI
dispatch_async(dispatch_get_main_queue(), ^{ self.textLabel.text = app.name; self.imageView.image = cache.imageCache[app.icon]; self.detailTextLabel.text = app.download; }); }); } - (void)loadImageWithURLOrFilePath:(NSString *)url isFilePath:(BOOL)isFilePath { SWPCache * cache = [SWPCache sharedInstance];
NSData * data = nil;
// 1.先判断下载该图片的操作是否已经执行过
// 如果执行过, 那么图片缓存中必定存在图片.
// if (!cache.operationCache[self.app.icon]) {
static int i = 0;
NSLog(@"---%d", i);
data = isFilePath ? [NSData dataWithContentsOfFile: url]
: (i++, [NSData dataWithContentsOfURL: [NSURL URLWithString: url ]]); // 如果数据下载失败
if (!data) { [cache.operationCache removeObjectForKey: self.app.icon]; } else { UIImage * image = [UIImage imageWithData: data]; cache.imageCache[self.app.icon] = image; cache.operationCache[self.app.icon] = [NSNumber numberWithBool: true]; // if (!isFilePath) {
// 1.为让其一边显示一边写入
//dispatch_async(dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[data writeToFile:url atomically: YES];
// });
// } } // } }

解决完UI界面的流畅度问题,我们就需要利用内存结构来节约用户流量和提高UI再次渲染的速度。

所以此时,还没将图片缓存到主存中,所以请看下面动态图。再将第12,24行打开,再看第二种动态图会发现,打印值只到16,也就是所只下载了
16次图片。这就大大提高了的说明了能节约用户流量和提高UI再次渲染的速度。
如下图(没加入主存时候)

利用内存结构及多线程优化多图片下载(IOS篇)

分析B: 接着我们来看问题B。也许这时候你觉得程序已经不存在问题了,确实,现在的程序是不存在问题了,但是可能会遇到问题。就是遇到一种十分
极端的情况,这种情况可以通过断网来进行模拟。(模拟数据量过大,或者下载速度太慢,此时用户不断滚动TableView)会造成,因为图片没下载好,也就还没缓存到主存,所以当要取图片的时候,到主存对应的位置
去取,却发现没有,这时候,就会调用网络下载,下载图片,就造成了不断重复的下载。
如下图

利用内存结构及多线程优化多图片下载(IOS篇)

解决B
这时候我们就需要某种标志来,标志该下载已经存在,不需要重新下载。所以我用了一个字典来映射各个下载图片的操作,在下载操作执行前从字典中取出,判断有没有该操作,有则不重复下载。这是可以打开第52,和82行即可,观察到效果。(记得打开网络!)
如下图

利用内存结构及多线程优化多图片下载(IOS篇)

分析C: 其实C问题所起来很好解决,阅读我的源代码,你可以看到第75行是在当前线程中写入数据到磁盘,这就造成了,要等待该写入操作完成后才退出该函数,接着才将渲染任务交给主线程。但是写入操作和渲染操作其实是可以同时进行的。所以我们可以在这里使用异步函数

解决C:
打开对应的注释(72和77), 验证就不在做了,可以自己打印时间观察。

所以对于多图片下载的问题我们主要是这么做:
1.通过多线程的方式,解决UI能流畅渲染,。
2.通过利用内存构架提高UI渲染的速度,并且解决了第一种图片重复下载问题。
3.通过标记操作,实现同一下载互斥,解决UITableViewCell重用机制造成的第二种图片重复下载问题。

具体判断逻辑与细节:

1.先判断主存缓存中有没有图片,如果没有进行第二步

2.判断磁盘有没有缓存的图片,如果有则直接加载进主存缓存中,并记录该次操作,如果没有进行第三步。

3.先判断该下载操作是否存在,如果存在,则不进行下载操作。如果不存在进行第四步。

4.从网络中下载图片,并且判断下载是否成功,如果成功下载,则记录该次下载操作,实现互斥。再将图片写入主存缓存,并开启另外一个线程将图片写入磁盘。

如果没有下载成功或者从磁盘中没有加载成功,则移除该次的下载标志, 解除该次下载互斥。

5.主线程直接从主存中的图片缓存位置来图片,渲染到UI界面。

利用内存结构及多线程优化多图片下载(IOS篇)的更多相关文章

  1. OpenCL入门:(三:GPU内存结构和性能优化)

    如果我们需要优化kernel程序,我们必须知道一些GPU的底层知识,本文简单介绍一下GPU内存相关和线程调度知识,并且用一个小示例演示如何简单根据内存结构优化. 一.GPU总线寻址和合并内存访问 假设 ...

  2. AJ学IOS(55)多线程网络之图片下载框架之SDWebImage

    AJ分享,必须精品 效果: 代码: - (NSArray *)apps { if (!_apps) { NSArray *dictArray = [NSArray arrayWithContentsO ...

  3. Android性能优化-减小图片下载大小

    原文链接 https://developer.android.com/topic/performance/network-xfer.html 内容概要 理解图片的格式 PNG JPG WebP 如何选 ...

  4. 2016 - 1- 19 利用多线程优化从网上加载图片的Demo

    // // ZZTableViewController.m // 多图片下载 // // Created by Mac on 16/1/19. // Copyright © 2016年 Mac. Al ...

  5. [Android] 对自定义图片浏览器经常内存溢出的一些优化

    首先关于异步加载图片可以参见 夏安明 的博客:http://blog.csdn.net/xiaanming/article/details/9825113 这篇文章最近有了新的更改,大概看了一下,内容 ...

  6. java线程基础巩固---多线程与JVM内存结构的关系及Thread构造函数StackSize的理解

    继续学习一下Thread的构造函数,在上次[http://www.cnblogs.com/webor2006/p/7760422.html]已经对如下构造都已经学习过了: 多线程与JVM内存结构的关系 ...

  7. android内存优化之图片压缩和缓存

    由于手机内存的限制和网络流量的费用现在,我们在加载图片的时候,必须要做好图片的压缩和缓存. 图片缓存机制一般有2种,软引用和内存缓存技术. 1.压缩图片:压缩图片要既不能模糊,也不能拉伸图片. 图片操 ...

  8. oracle 初探内存结构

    数据库的存储机构 分为 逻辑存储结构 和 物理存储结构 逻辑存储结构: 数据库.表空间.段.区.块         物理存储结构: 数据库.控制文件.数据文件.初始化参数文件.OS块等. 一个区只能在 ...

  9. 管中窥豹——从对象的生命周期梳理JVM内存结构、GC调优、类加载、AOP编程及性能监控

    如题,本文的宗旨既是透过对象的生命周期,来梳理JVM内存结构及GC相关知识,并辅以AOP及双亲委派机制原理,学习不仅仅是海绵式的吸收学习,还需要自己去分析why,加深对技术的理解和认知,祝大家早日走上 ...

随机推荐

  1. HTML 5 音频(audio)

     audio 元素支持三种音频格式 IE 9 Firefox 3.5 Opera 10.5 Chrome 3.0 Safari 3.0 Ogg Vorbis   √ √ √   MP3 √     √ ...

  2. win10+vs2015+opencv3.0 x86/x64配置(debug+release)

    最近做一些图像识别的项目,用到了opencv,opencv3.1没有x86版本,所以只能用opencv3.0来完成,下面介绍一下在window10下vs2015 配置opencv3.0的过程(x86和 ...

  3. (译文)12个简单(但强大)的JavaScript技巧(二)

    原文链接: 12 Simple (Yet Powerful) JavaScript Tips 其他链接: (译文)12个简单(但强大)的JavaScript技巧(一) 强大的立即调用函数表达式 (什么 ...

  4. 24种设计模式--状态模式【State Pattern】

    现在城市发展很快,百万级人口的城市一堆一堆的,那其中有两个东西的发明在城市的发展中起到非常重要的作用:一个是汽车,一个呢是...,猜猜看,是什么?是电梯!汽车让城市可以横向扩展,电梯让城市可以纵向延伸 ...

  5. redux 中间件 --- applyMiddleware 源码解析 + 中间件的实战

    前传  中间件的由来 redux的操作的过程,用户操作的时候,我们通过dispatch分发一个action,纯函数reducer检测到该操作,并根据action的type属性,进行相应的运算,返回st ...

  6. 【Selenium-WebDriver自学】Selenium-IDE调试(四)

    ==================================================================================================== ...

  7. MySQL之开启远程连接

    MySQL安装时,默认只能本地连接. mysql -u root -p mysql>use mysql; mysql>select 'host' from user where user= ...

  8. Oracle EBS OM 发放订单

    DECLARE l_header_rec OE_ORDER_PUB.Header_Rec_Type; l_line_tbl OE_ORDER_PUB.Line_Tbl_Type; l_action_r ...

  9. Could not install packages due to an Environment Error: [Errno 13] Permission denied 解决方案

    执行pip install 报错如下: Could not install packages due to an Environment Error: [Errno 13] Permission de ...

  10. Activity相关知识点总结

    一.Activity状态 Activity有三种状态:active/running.paused.stopped. 1.active/running状态,在当前屏幕时,即用户可见的Activity,位 ...