其实有些框架的实现原理,并没有想象中那么难,思想也很简单,主要是更新第三方框架的作者对自己写的代码,进行了多层封装,使代码的可读性降低,也就使得框架看起来比较难.我来实现以下SDWebimage的的曾实现.

实现过程中可能遇到的问题:

1.UI卡顿: 当界面中需要下载多张图片的时候,由于图片下载是耗时操作,会短暂的占据着主线程的执行,也就会是UI界面看起来卡顿.

  解决办法: 需要把耗时操作放在子线程中执行(若是对多线程不了解,我之前的博客写过关于多线程的知识)

 NSBlockOperation *download = [self.operations objectForKey:app.icon];

            if (download) {

                NSLog(@"该图片正在下载,请稍等");
}else
{
//封装下载图片的操作
download = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:app.icon]; //耗时操作,模拟网速慢的情况
for (NSInteger i =; i <; i++) { } NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data]; NSLog(@"下载第%zd行cell对应的图片",indexPath.row); //容错处理
if (image == nil) {
[self.operations removeObjectForKey:app.icon];
return ;
} //保存图片到内存缓存中
[self.images setObject:image forKey:app.icon]; //保存数据到沙盒(磁盘缓存)
[data writeToFile:fullPath atomically:YES]; //线程间通信 主线程中设置图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//cell.imageView.image = image;
//刷新
//[tableView reloadData];
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
}];
}]; //把操作保存到操作缓存中
[self.operations setObject:download forKey:app.icon]; //把操作添加到队列
[self.queue addOperation:download];
}

2.重复加载问题(一)

在下载图片的地方,打印相关的下载的图片的信息,你会发现图片会重复下载

解决办法: 1.内存缓存处理  2.二级缓存处理

内存的大小毕竟有限的,在开发中我们一般采用二级缓存处理.

二级缓存处理过程: 
       1.在显示图片之前,先检查内存缓存中时候有该图片
       2.如果内存缓存中有图片,那么就直接使用,不下载
       3.如果内存缓存中无图片,那么再检查是否有磁盘缓存
       4.如果磁盘缓存中有图片,那么直接使用,还需要保存一份到内存缓存中(方便下一次使用)
       5.如果磁盘缓存中无图片,那么再去下载,并且把下载完的图片保存到内存缓存和磁盘缓存
 //保存图片到内存缓存中
[self.images setObject:image forKey:app.icon]; //保存数据到沙盒(磁盘缓存)
[data writeToFile:fullPath atomically:YES];

3.图片不显示

原因: 图片的显示操作是异步执行的,也就是说需要重新刷新该行的cell

 //线程间通信 主线程中设置图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//cell.imageView.image = image;
//刷新
//[tableView reloadData];
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
}];

4.图片重复下载问题(二)

这种情况需要模拟一定的情况,例如网络信号不好(网络信号不好可以用耗时操作代替),造成这种情况的原因,就是在网络情况不好的情况下,用户重复滑动.就会重复的发送下载图片的请求,最终造成重复下载.

解决办法: 我在这里用的操作队列,对应的解决办法,就是把对应的事件以字典的方式存储到内存,放置重复发送下载图片的请求

 //把操作保存到操作缓存中
[self.operations setObject:download forKey:app.icon];

5.图片显示错乱的问题

由于cell的重用导致,用户下拉或者上拉,当网络不好的情况,该cell的图片还没有被加载,但是对应的cell已经被显示,就会显示cell被重用之前的数据,造成数据混乱

解决办法: 设置每个cell中image为nil或者设置默认图片.

6.中间还要加一些容错处理.

例如: 若是服务器返回的url是错误的,就会造成程序闪退,需要做处理

 NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data]; NSLog(@"下载第%zd行cell对应的图片",indexPath.row); //容错处理
if (image == nil) {
[self.operations removeObjectForKey:app.icon];
return ;
}

7.内存缓存的处理

-(void)didReceiveMemoryWarning
{
//清空内存缓存
[self.images removeAllObjects]; //取消队列中所有的操作
[self.queue cancelAllOperations];
}

完整的代码如下:

模型中的代码:

#import <Foundation/Foundation.h>

@interface BOAppModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *icon;
@property (nonatomic, strong) NSString *download; - (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)appModelWithDict:(NSDictionary *)dict;
@end
#import "BOAppModel.h"

@implementation BOAppModel
- (instancetype)initWithDict:(NSDictionary *)dict{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)appModelWithDict:(NSDictionary *)dict{ return [[BOAppModel alloc] initWithDict:dict];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{ }
@end

控制器空代码如下:

#import "ViewController.h"
#import "BOAppModel.h" @interface ViewController ()
@property (nonatomic, strong) NSArray *apps;
@property (nonatomic, strong) NSCache *images;
@property (nonatomic, strong) NSMutableDictionary *operations;
@property (nonatomic, strong) NSOperationQueue *queue;
@end @implementation ViewController #pragma mark ------------------
#pragma mark lazy loading -(NSOperationQueue *)queue
{
if (_queue == nil) {
_queue = [[NSOperationQueue alloc]init];
}
return _queue;
} -(NSCache *)images
{
if (_images == nil) {
_images = [[NSCache alloc] init];
}
return _images;
}
-(NSMutableDictionary *)operations
{
if (_operations == nil) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
} -(NSArray *)apps
{
if (_apps == nil) { //加载plist文件中的数据
NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]]; //字典转模型(字典数组-->模型数组)
NSMutableArray *arr = [NSMutableArray array];
for (NSDictionary *dict in arrayM) {
[arr addObject: [BOAppModel appModelWithDict:dict]];
} _apps = arr; }
return _apps;
} #pragma mark ------------------
#pragma mark UItableViewDataSource -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return ;
} -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
} -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//01 创建cell
static NSString *ID = @"app";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; //02 设置cell
//001 拿到该行cell对应的数据
XMGApp *app = self.apps[indexPath.row];
//002 设置标题
cell.textLabel.text = app.name;
//003 设置子标题
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",app.download];
//004 设置图片 /*
内存缓存处理:
[1]在显示图片之前,先检查缓存中是否有该图片(是否已经下载过)
[2]如果缓存中有图片,那么就直接使用,不下载
[3]如果缓存中无图片,那么再去下载,并且把下载完的图片保存到内存缓存
*/
/*
二级(内存-磁盘)缓存处理:
[1]在显示图片之前,先检查内存缓存中是否有该图片
[2]如果内存缓存中有图片,那么就直接使用,不下载
[3]如果内存缓存中无图片,那么再检查是否有磁盘缓存
[4]如果磁盘缓存中有图片,那么直接使用,还需要保存一份到内存缓存中(方便下一次)
[5]如果磁盘缓存中无图片,那么再去下载,并且把下载完的图片保存到内存缓存和磁盘缓存
*/ //检查内存缓存
UIImage *image = [self.images objectForKey:app.icon]; if (image)
{
cell.imageView.image = image;
NSLog(@"第%zd行cell对应的图片从内存缓存中获取",indexPath.row);
}else
{
//文件名称
NSString *fileName = [app.icon lastPathComponent]; //获得缓存路径
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; //拼接文件的全路径
NSString *fullPath = [cache stringByAppendingPathComponent:fileName]; //检查磁盘缓存
NSData *data = [NSData dataWithContentsOfFile:fullPath];
data = nil;
if (data)
{
//如果有磁盘缓存,那么久直接使用
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image; //还需要保存一份到内存缓存中
[self.images setObject:image forKey:app.icon]; NSLog(@"第%zd行cell对应的图片使用了磁盘缓存--%@",indexPath.row,fullPath);
}else
{
//开子线程下载图片
/* 对操作进行缓存处理
如果没有内存缓存也没有磁盘缓存,则
001 先检查该图片的下载操作是否已经存在(该图片是否正在下载)
002 如果下载操作已经存在,那么就等待即可
003 如果下载操作不存在,那么再去下载
*/ //清空cell的图片
//cell.imageView.image = nil;
//设置占位图片
cell.imageView.image = [UIImage imageNamed:@"Snip20161203_14"]; NSBlockOperation *download = [self.operations objectForKey:app.icon]; if (download) { NSLog(@"该图片正在下载,请稍等");
}else
{
//封装下载图片的操作
download = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:app.icon]; //耗时操作,模拟网速慢的情况
for (NSInteger i =; i <; i++) { } NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data]; NSLog(@"下载第%zd行cell对应的图片",indexPath.row); //容错处理
if (image == nil) {
[self.operations removeObjectForKey:app.icon];
return ;
} //保存图片到内存缓存中
[self.images setObject:image forKey:app.icon]; //保存数据到沙盒(磁盘缓存)
[data writeToFile:fullPath atomically:YES]; //线程间通信 主线程中设置图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//cell.imageView.image = image;
//刷新
//[tableView reloadData];
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
}];
}]; //把操作保存到操作缓存中
[self.operations setObject:download forKey:app.icon]; //把操作添加到队列
[self.queue addOperation:download];
}
}
} //03 返回cell
return cell;
} -(void)didReceiveMemoryWarning
{
//清空内存缓存
[self.images removeAllObjects]; //取消队列中所有的操作
[self.queue cancelAllOperations];
} /*
001 UI卡顿 ---> 开子线程下载图片
002 重复下载 --->内存缓存 -->磁盘缓存
003 图片不显示 --->图片的显示操作是异步执行的
004 新的重复下载
005 图片显示错乱的问题-->cell的重用
006 自己寻找
*/
/*
沙盒文件:
Documents 官方规定不能缓存处理
Library
cache 缓存文件
偏好设置
Tmp 临时文件存储路径(随时可能被删除)
*/
@end

iOS-SDWebimage底层实现原理的更多相关文章

  1. iOS分类底层实现原理小记

    摘要:iOS分类底层是怎么实现的?本文将分如下四个模块进行探究分类的结构体编译时的分类分类的加载总结本文使用的runtime源码版本是objc4-680文中类与分类代码如下//类@interfaceP ...

  2. SDWebImage底层实现原理

    SDWebImage底层实现有沙盒缓存机制,主要由三块组成 1.内存图片缓存2.内存操作缓存3.磁盘沙盒缓存内部实现过程:第一步,下载SDWebImage,导入工程. 第二步,在需要的地方导入头文件 ...

  3. iOS weak底层实现原理

    今年年底做了很多决定,离开工作三年的深圳,来到了上海,发现深圳和上海在苹果这方面还是差距有点大的,上海的市场8成使用swift编程,而深圳8成的使用OC,这点还是比较让准备来上海打拼的苹果工程师有点小 ...

  4. iOS 开发之 SDWebImage 底层实现原理分析

    SDWebImage 是一个比较流行的用于网络图片缓存的第三方类库.这个类库提供了一个支持缓存的图片下载器.为了方便操作者调用,它提供了很多 UI 组件的类别,例如:UIImageView.UIBut ...

  5. IOS SDWebImage实现原理详解

    在之前我写过SDWebImage的使用方法,主要是用与获取网络图片,没有看过的朋友可以看看. 这篇文章将主要介绍SDWebImage的实现原理,主要针对于获取网络图片的原理,如果没有第三方我们该怎么去 ...

  6. 【如何快速的开发一个完整的iOS直播app】(原理篇)

    原文转自:袁峥Seemygo    感谢分享.自我学习 目录 [如何快速的开发一个完整的iOS直播app](原理篇) [如何快速的开发一个完整的iOS直播app](播放篇) [如何快速的开发一个完整的 ...

  7. iOS:app直播---原理篇

    [如何快速的开发一个完整的iOS直播app](原理篇) 转载自简书@袁峥Seemygo:http://www.jianshu.com/p/7b2f1df74420   一.个人见解(直播难与易) 直播 ...

  8. PHP底层工作原理

    最近搭建服务器,突然感觉lamp之间到底是怎么工作的,或者是怎么联系起来?平时只是写程序,重来没有思考过他们之间的工作原理: PHP底层工作原理 图1 php结构 从图上可以看出,php从下到上是一个 ...

  9. iOS app 程序启动原理

    iOS app 程序启动原理 Info.plist: 常见设置     建立一个工程后,会在Supporting files文件夹下看到一个"工程名-Info.plist"的文件, ...

  10. iOS App签名的原理

    前言 相信很多同学对于iOS的真机调试,App的打包发布等过程中的各种证书.Provisioning Profile. CertificateSigningRequest.p12的概念是模糊的,导致在 ...

随机推荐

  1. MongoDB的简单操作(asp.net)

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using MongoDB.D ...

  2. Android进程间通讯

    最近研究了一下Android进程间通讯,原来只是会用,但是只是会用是不行滴,就来研究一下. 刚开始看的时候,我的头是这么大,看了一夜的时候,头就变成这样了,,吓得宝宝赶紧上床休息了,. 先喝喝茶讲个故 ...

  3. Allegro之无法保存(提示和用户有关或者和lock有关)

    使用中无意出现此情况 无奈重新打开文件时发现brd文件下面有个.brd.lck文件,顺手删掉,回复正常~ 此为bug解bug,具体方法下次遇到再仔细研究是为什么~ 养成隔几分钟手动保存的好习惯,防止b ...

  4. fasicon.js (无二维码版,如需要js创建二维码版本可联系我:770959294@qq.com)

    /*** Created by 张云山 on 2016/12/9.*/(function(wins,doct){wins.fasicon = function(){this.name = " ...

  5. socket编程学习step1

    socket学习参考链接,赞一个:http://blog.csdn.net/hguisu/article/details/7445768 sockets(套接字)编程有三种,流式套接字(SOCK_ST ...

  6. 领域驱动有感<上>

    最近看了<领域驱动设计:软件核心复杂性应对之道>,从字面上来看领域驱动就是解决软件复杂性问题的:然而领域驱动设计的门槛很高,没有很深厚的面向对象编码能力几乎不可能实践成功.Martin F ...

  7. 四、jquery中的事件与应用

    当用户浏览页面时,浏览器会对页面代码进行解释或编译--这个过程实质上是通过时间来驱动的,即页面在加载时,执行一个Load事件,在这个事件中实现浏览器编译页面代码的过程.时间无论在页面元素本身还是在元素 ...

  8. 谢欣伦 - OpenDev原创教程 - 蓝牙设备查找类CxBthRadio & CxBthRadioFind

    这是一个精练的蓝牙设备查找类,类名.函数名和变量名均采用匈牙利命名法.小写的x代表我的姓氏首字母(谢欣伦),个人习惯而已,如有雷同,纯属巧合. CxBthRadioFind的使用如下: void CU ...

  9. 用itextsharp打印pdf示例

    学习了用itextsharp输出PDF文档,支持用XML定义文档,可定义多个数据源,简单的表达式,用于项目中效果还不错,其中PdfPageEvent类由包大人提供. 示例程序定义了一个简单的xml文件 ...

  10. maven 本地仓库和远程仓库

    在Maven中,任何一个依赖.插件或者项目构建的输出,都可以称之为构件. Maven在某个统一的位置存储所有项目的共享的构件,这个统一的位置,我们就称之为仓库.(仓库就是存放依赖和插件的地方) 任何的 ...