SDWebImage设计思路及对我们项目的启迪。

1.要实现网络图片下载首先要思考几个问题。

1>.要在异步线程中执行,否则会阻塞主线程(线程管理)

2>.考虑图片是否需要下载(1.已经确定失效的URL不必下。2.已经缓存(内存、磁盘)的图片不必下。)

3>.同一个URL不要重复下载

4>.缓存策略

2.SDWebImage的主要功能

1>.实现了UIImageView的扩展,一行代码实现异步下载图片的功能,通过block回调下载进度和下载状态。


headerImage?.sd_setImage(with: url, placeholderImage: image, options: 0, progress: { (receivedSize, expectedSize) in

//返回进度

}, completed: { (image, error, type, url) in

//进度返回

})

2>.使用 SDImageCache 异步缓存图片


//添加内存缓存图片默认

SDImageCache.shared().store(image, forKey: imageKey)


//读取内存缓存图片

SDImageCache.shared().queryDiskCache(forKey: imageKey) { (image, type) in

}

3>.有时候,一张图片的 URL 中的一部分可能是动态变化的(比如获取权限上的限制),所以我们只需要把 URL 中不变的部分作为缓存用的 key,通过传入代码块,来实现自定义设置key值。


SDWebImageManager.shared().cacheKeyFilter = { (url) -> String in

//巴拉巴拉返回字符串

}

3.从SDWebImage的类开始介绍

1>.SDWebImageManager主要负责串联图片缓存和图片下载逻辑,先看下最重要的两个属性


//SDImageCache负责缓存逻辑

@property (strong, nonatomic, readwrite) SDImageCache *imageCache;

//SDWebImageDownloader下载器负责下载任务管理

@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;

2>.SDWebImageDownloader用来下载图片和优化图片加载的。


//下载任务队列,存储着每个下载任务SDWebImageDownloaderOperation,也是通过NSOperationQueue的属性设置最大并发数的。

@property (strong, nonatomic) NSOperationQueue *downloadQueue;

//图片下载的回调 block 都是存储在这个属性中,该属性是一个字典,key 是图片的 URL,value 是一个数组,包含每个图片的多组回调信息。这个数组会保存调用方法的闭包,如果url对应的闭包可以找到,则说明该图片已经在下载中了不会添加下载任务,如果没找到会创建下载任务,并且任务回调的时候回把url对应的所有闭包都回调,这样避免了同一个URL重复下载,实现了不同控件共享同一个下载任务,后面介绍方法的时候会详述,用 JSON 格式表示如下{

"url1": [

{

"kProgressCallbackKey": "progressCallback1_1",

"kCompletedCallbackKey": "completedCallback1_1"

},

{

"kProgressCallbackKey": "progressCallback1_2",

"kCompletedCallbackKey": "completedCallback1_2"

}

],

"url2": [

{

"kProgressCallbackKey": "progressCallback2_1",

"kCompletedCallbackKey": "completedCallback2_1"

},

{

"kProgressCallbackKey": "progressCallback2_2",

"kCompletedCallbackKey": "completedCallback2_2"

}

]

}

@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;

接下来看一下SDWebImageDownloader的核心方法- downloadImageWithURL: options: progress: completed:,该方法首先调用了-addProgressCallback: andCompletedBlock: forURL: createCallback:,这个方法就是判断URLCallbacks中字典元素中对应的闭包是否存在(存在说明已经在下载中了,不存在说明第一次下载),如果不存在回调createCallback这个block,并将传入的进度、状态block保存到URLCallbacks中,注意由于多个线程可能访问URLCallbacks用barrierQueue来保卫下防止数据竞争,然后在SDWebImageDownloader 的createCallback回调中创建下载任务SDWebImageDownloaderOperation,并添加到SDWebImageDownloader下载器的任务队列中,利用NSOperationQueue的特性,添加到队列中的OP会自动执行。接下来我们看下SDWebImageDownloaderOperation。

3>.SDWebImageDownloaderOperation继承自NSOperation,NSOperation可以通过重写main和start方法去实现异步操作。该类才是真正处理下载任务的类。通过下载器传入的request、闭包如下


- (id)initWithRequest:(NSURLRequest *)request

options:(SDWebImageDownloaderOptions)options

progress:(SDWebImageDownloaderProgressBlock)progressBlock

completed:(SDWebImageDownloaderCompletedBlock)completedBlock

cancelled:(SDWebImageNoParamsBlock)cancelBlock {

if ((self = [super init])) {

_request = request;

_shouldDecompressImages = YES;

_shouldUseCredentialStorage = YES;

_options = options;

_progressBlock = [progressBlock copy];

_completedBlock = [completedBlock copy];

_cancelBlock = [cancelBlock copy];

_executing = NO;

_finished = NO;

_expectedSize = 0;

responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called

}

return self;

}

通过NSURLConnection下载


self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
[self.connection start];

由NSURLConnectionDelegate回调下载状态。由于闭包是下载器SDWebImageDownloader 的- downloadImageWithURL: options: progress: completed:方法传入的,所以在SDWebImageDownloader中返回,返回后在URLCallbacks取出所有url对应的闭包进行回调。
如上这就是下载的全过程。下载逻辑并没有那么复杂,之所以难懂是因为对block的频繁操作和NSOperation的有点反人类思维的执行时机(要适应计算机思维~~)。接下来看下缓存逻辑

4>.SDImageCache类SDImageCache的内存缓存是通过一个NSCache类来实现的,NSCache比较类似于可变类型字典通过键值对存储的容器,它会有一个自动删除机制,内存紧张的时候NSCache回自动删除一些对象,并且他还是线程安全的,不用加锁。

4.对比我们的下载项目:

我们项目中的下载管理类更类似于SDWebImageDownloader,是通过NSOperationQueue类操作课件音频的下载任务,本地数据库和SDK结合的方式管理视频下载,如下图
屏幕快照 2017-11-26 下午4.03.40.png

从SDWebImage设计思路得到的启示:

1.现在我们项目中下载逻辑用通知回调在tableView查找cell的时候效率较低,可能会造成UI更新不及时的情况,优化:可以用字典保存闭包的方式用闭包回调下载状态和进度。这样会提高代码效率,且可读性很高。(这期争取实现!)

2.由于SDWebImage功能较强大,代码较多,可以借鉴SDWebImage的设计思路自己实现一个轻量级的图片缓存框架,方便维护。

5.最后说下我们对图片压缩的优化:

经过下载多张头像图片,发现server返回的头像图片大小都在100-200kb之间,但是头像并没有对清晰度那么高的要求,所以在SD的源码基础上进行修改。在异步操作SDWebImageDownloaderOperation中,下载完毕后对图片进行压缩。

策略:具体策略是参考微信,微信头像图片大小都在32kb左右,而apple提供的api有两种压缩图片的方法,一种按照质量压缩(会最大限度的保证图片质量,压缩到一定程度不进行压缩),一种是尺寸压缩(会有损图片质量),我们先用二分法循环6次看能否将图片大小保证在32kb-32kb*0.9之间,如果可以直接返回图片,如果不行,以32kb为标准按尺寸压缩,具体实现如下:


- (UIImage *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength {

// Compress by quality

CGFloat compression = 1;

NSData *data = UIImageJPEGRepresentation(image, compression);

NSLog(@"压缩前%lu",(unsigned long)data.length);

if (data.length < maxLength) return image;

CGFloat max = 1;

CGFloat min = 0;

for (int i = 0; i < 6; ++i) {

compression = (max + min) / 2;

data = UIImageJPEGRepresentation(image, compression);

if (data.length < maxLength * 0.9) {

min = compression;

} else if (data.length > maxLength) {

max = compression;

} else {

break;

}

}

UIImage *resultImage = [UIImage imageWithData:data];

if (data.length < maxLength) {

NSLog(@"压缩后%lu",(unsigned long)data.length);

return resultImage;

}

// Compress by size

NSUInteger lastDataLength = 0;

while (data.length > maxLength && data.length != lastDataLength) {

lastDataLength = data.length;

CGFloat ratio = (CGFloat)maxLength / data.length;

CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)),

(NSUInteger)(resultImage.size.height * sqrtf(ratio))); // Use NSUInteger to prevent white blank

UIGraphicsBeginImageContext(size);

[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];

resultImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

data = UIImageJPEGRepresentation(resultImage, compression);

}

NSLog(@"压缩后%lu",(unsigned long)data.length);

return resultImage;

}

效果还是较明显的哦!

屏幕快照 2017-11-26 下午4.26.18.png

最后SD中对于细节的处理还是很值得我们研究的(例如对循环引用的处理,线程间数据的处理,内存方面的考虑),有时间还会继续更新。

写在最后:编程就是一个把复杂任务不断的分割成更小更简单的部分,然后去实现这些小部分的过程,不应该是边写边分割,功能实现了就行呗,这往往是写不出好代码的原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容