美文网首页
SDWebImage设计思路及对我们项目的启迪。

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

作者: 我是繁星 | 来源:发表于2017-11-26 16:40 被阅读0次

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

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

    相关文章

      网友评论

          本文标题:SDWebImage设计思路及对我们项目的启迪。

          本文链接:https://www.haomeiwen.com/subject/shokbxtx.html