SDWebImage的原理及简单实现

/ 0评 / 0

多线程我们一般用在哪里呢?之前都已经说过了,使用多线程是为了防止做大运算量的操作阻塞了主线程,使我们的程序出现卡顿的现象,我们可以把大的运算放在新开的线程里,当然其实我们开发中主要用到的是加载大量的网络数据的时候,比如我们下载一个图片,那数据量肯定比文本多的多啊,那不就会有卡顿现象,那就开一个线程来下载,加载完在主线程里刷新UI,还可以做一下缓存,更省流量。
就拿我们开发中常用的三方库 SDWebImage 来说吧,下面先看一下它的逻辑。

webImage原理

当拿到一个URI的时候先通过URI在系统的内存中查找这张图,如果找不到就去手机的缓存中去查找,如果还找不到那就说明我们本地没有这个图片,就去网上下载。
我们这里来简单实现一下这个逻辑

// DCImageCache.h
@interface DCImageCache : NSObject{
//相当于存储在内存中
NSMutableDictionary *_allCache;
}

+ (DCImageCache *)sharedImageCache;

/**
* 从内存中查找图片
*
* @param urlString 图片地址
*
* @return 图片
*/
- (UIImage *)seachImageFromMemoryWithURLString:(NSString *)urlString;

/**
* 从沙盒中查找图片
*
* @param urlString 图片地址
*
* @return 图片
*/
- (UIImage *)searchImageFromSandBoxWithURLString:(NSString *)urlString;

/**
* 下载图片
*
* @param urlString 图片地址
* @param block 回调
*/
- (void)downloadImageWithURLString:(NSString *)urlString completionBlock:(void (^)(UIImage *image))block;

/**
* 清除图片所占磁盘空间
*/
- (void)clearDisk;
@end

// DCImageCache.m
#import "DCImageCache.h"
@implementation DCImageCache
static DCImageCache *imageCache = nil;
//单例类
+(DCImageCache *)sharedImageCache{
//上锁
@synchronized (self) {
if (imageCache == nil) {
imageCache = [[DCImageCache alloc] init];
}
}
return imageCache;
}

+ (id)alloc{
@synchronized (self) {
if (imageCache == nil) {
imageCache = [super alloc];
}
}
return imageCache;
}

- (id)init{
self = [super init];
if (self) {
_allCache = [[NSMutableDictionary alloc] init];

//当系统发出内存警告的时候自动清除内存
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}

return self;
}

//清除内存
- (void)clearMemory{
NSLog(@"清除内存中缓存的图片");
[_allCache removeAllObjects];
}

- (UIImage *)seachImageFromMemoryWithURLString:(NSString *)urlString{
//从内存(字典)中找图片
//假设在字典中 图片地址是键 UIImage对象是值
return [_allCache objectForKey:urlString];
}

//获取内存大小
- (long long)getMemorySize{
long long size = 0;
for (NSString *key in _allCache.allKeys) {
UIImage *image = [_allCache objectForKey:key];
NSData *data = UIImageJPEGRepresentation(image, 1);
size += data.length;
}
return size;
}

//清除图片所占的存储
- (void)clearDisk{
//图片缓存的位置
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/DCImageCache"];
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}

- (UIImage *)searchImageFromSandBoxWithURLString:(NSString *)urlString{
//我们默认的沙盒存储路径为 ~/Documents/DCImageCache/图片的地址为文件名
NSString *path = [self getPathWithFileName:urlString];
UIImage *image = [UIImage imageWithContentsOfFile:path];
if (image) {
//缓存到内存
[_allCache setObject:image forKey:urlString];
NSLog(@"内存中的图片 _allCache %@", _allCache);
}
return image;
}

//根据文件名获取文件的路径
- (NSString *)getPathWithFileName:(NSString *)fName{
NSString *dPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/DCImageCache"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:dPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *name = [[fName componentsSeparatedByString:@"/"] lastObject];
NSString *fPath = [NSString stringWithFormat:@"%@/%@",dPath,name];
NSLog(@"fPath %@", fPath);
return fPath;
}

- (void)downloadImageWithURLString:(NSString *)urlString completionBlock:(void (^)(UIImage *))block{
//使用GCD来下载图片,先获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//异步操作
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
UIImage *image = [UIImage imageWithData:data];

//回到主队列进行操作
dispatch_async(dispatch_get_main_queue(), ^{
block(image);

if (image != nil) {
//缓存到内存
[_allCache setObject:image forKey:urlString];
NSLog(@"内存中的图片 _allCache %@", _allCache);
//缓存到沙盒
BOOL isSuccess = [data writeToFile:[self getPathWithFileName:urlString] atomically:YES];

NSLog(@"缓存到沙盒 isSuccess %d",isSuccess);
}
});
});

}
@end

下面我们通过一个UIImageView的扩展类来做这些操作,这样更方便我们的使用

// UIImageView+DCWebCache.h
#import <UIKit/UIKit.h>
#import "DCImageCache.h"

@interface UIImageView (DCWebCache)

/**
* 根据URL加载图片
*
* @param urlString 图片的URL
* @param image 占位图
*/
- (void)setImageWithURLString:(NSString *)urlString placeholederImage:(UIImage *)image;

/**
* 根据URL加载图片
*
* @param urlString 图片URL
*/
- (void)setImageWithURLString:(NSString *)urlString;
@end

// UIImageView+DCWebCache.m
#import "UIImageView+DCWebCache.h"
@implementation UIImageView (DCWebCache)
- (void)setImageWithURLString:(NSString *)urlString placeholederImage:(UIImage *)image{
//先使用占位图
self.image = image;

[self setImageWithURLString:urlString];
}

- (void)setImageWithURLString:(NSString *)urlString{
DCImageCache *imageCache = [DCImageCache sharedImageCache];

UIImage *image = nil;
//1.从内存中查找图片
image = [imageCache seachImageFromMemoryWithURLString:urlString];
if (image != nil) {
self.image = image;
return;
}

//2.从沙盒中照图片
image = [imageCache searchImageFromSandBoxWithURLString:urlString];
if (image != nil) {
self.image = image;
return;
}

//3.从网络上获取
[imageCache downloadImageWithURLString:urlString completionBlock:^(UIImage *image) {
self.image = image;
}];
}
@end

我们可以看到实现起来其实很简单,下面我们来调用一下

- (void)viewDidLoad {
[super viewDidLoad];

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 60, 320, 400)];
[self.view addSubview:imageView];

[imageView setImageWithURLString:@"http://h.hiphotos.baidu.com/image/pic/item/8601a18b87d6277f017b73322b381f30e924fc4b.jpg"];
}

第一次运行结果:

jieguo1

第二次运行结果:

jieguo2

我们第一次运行的时候缓存了,当第二次运行就不再请求网络图片了

代码地址 : https://git.oschina.net/zcb1603999/iOSTools

评论已关闭。