解析iOS开源项目--SDWebImage

作者: 小兵快跑 | 来源:发表于2016-07-26 09:19 被阅读919次

都说阅读源代码对于功力的提升是十分显著的, 但是很多的著名开源框架源代码动辄上万行, 复杂度实在太高, 之前我曾经试图读一些开源框架的源代码, 比如说 AFNetworking, SDWebImage, ReactiveCocoa 但是由于当时比较浮躁, 实在没法静下心来看, 而且有一些急功近利, 所以面对宝藏实在无力挖掘.
而最近, 由于时间比较充裕, 也终于能静下心来一段一段分析这些著名项目的源代码, 也准备开始写一些关于 iOS 源代码分析和整理,希望能对各位开发者有所帮助。

首先来介绍一下这个 SDWebImage 这个著名开源框架吧, 这个开源框架的主要作用就是:一个异步下载图片并且支持缓存的 UIImageView 分类.框架中最最常用的方法其实就是这个:

[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]
                  placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

我们已经看到了这个框架简洁的接口, 接下来我们看一下 SDWebImage 是用什么样的方式优雅地实现异步加载图片和缓存的功能呢?

使用场景:自定义的UITableViewCell上有图片需要显示,要求网络网络状态为WiFi时,显示图片高清图;网络状态为蜂窝移动网络时,显示图片缩略图。

SDWebImage 流程

SDWebImage使用流程

让读者你更容易理解,我先贴出伪代码:

- setItem:(Model *)item
{
    _item = item;
    if (缓存中有原图) 
    {
        self.imageView.image = 原图;
    } else 
    {
        if (Wifi环境) 
        {
            下载显示原图
        } else if (手机自带网络) 
        {
            if (3G\4G环境下仍然下载原图) 
            {
                下载显示原图
            } else 
            {
                下载显示小图
            }
        } else 
        {
            if (缓存中有小图) 
            {
                self.imageView.image = 小图;
            } else  // 处理离线状态
            {
                self.imageView.image = 占位图片;
            }
        }
    }
}

实现上面的伪代码:读者可以一一对应上面的伪代码。练习的时候推荐先写伪代码,再写真实代码

- setItem:(CustomItem *)item
{
    _item = item;
  // 占位图片
    UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"];
    // 从内存\沙盒缓存中获得原图
    UIImage *originalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.originalImage];
    if (originalImage) { // 如果内存\沙盒缓存有原图,那么就直接显示原图(不管现在是什么网络状态)
        [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
    } else { // 内存\沙盒缓存没有原图
        AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
        if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下载原图
            [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
        } else if (mgr.isReachableViaWWAN) { // 在使用手机自带网络
            //     用户的配置项假设利用NSUserDefaults存储到了沙盒中
            //    [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"alwaysDownloadOriginalImage"];
            //    [[NSUserDefaults standardUserDefaults] synchronize];
#warning 从沙盒中读取用户的配置项:在3G\4G环境是否仍然下载原图
            BOOL alwaysDownloadOriginalImage = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysDownloadOriginalImage"];
            if (alwaysDownloadOriginalImage) { // 下载原图
                [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
            } else { // 下载小图
                [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];
            }
        } else { // 没有网络
            UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.thumbnailImage];
            if (thumbnailImage) { // 内存\沙盒缓存中有小图
                [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];
            } else {
                [self.imageView sd_setImageWithURL:nil placeholderImage:placeholder];
            }
        }
    }
}

SDWebImage 使用

图片下载并缓存方法(内存缓存&磁盘缓存):

//得到当前图片的url
- (NSURL *)sd_imageURL;

/**
 * 异步下载图片并缓存
 */
- (void)sd_setImageWithURL:(NSURL *)url;

/**
 * 异步下载图片并缓存,没下载完之前先显示占位图片,下载完之后再替换
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;

/**
 * 异步下载图片并缓存
 * @param url           下载图片路径
 * @param placeholder   占位图片,直到下载完成才替换
 * @param options       下载图片选择方式
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;

/**
 * 异步下载图片并缓存,完成后可以在block中做事情
 * @param url           下载图片url
 * @param completedBlock  
  SDWebImageCompletionBlock:UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL)
  block中可以得到下载图片,错误信息,缓存类型,下载图片地址  参数,给用户做相应操作
 */
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;

/**
 * 异步下载图片并缓存,提供占位图片,并完成后可以在block中做事情
 *
 * @param url            T下载图片url
 * @param placeholder    T占位图片,直到下载完成才替换
 * @param completedBlock
 SDWebImageCompletionBlock:UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL)
 block中可以得到下载图片,错误信息,缓存类型,下载图片地址  参数,给用户做相应操作
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;

/**
 * 异步下载图片并缓存,完成后可以在block中做事情
 * @param url           下载图片路径
 * @param placeholder   占位图片,直到下载完成才替换
 * @param options       下载图片选择方式
 * @param completedBlock  同上
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;

/**
 * 异步下载图片并缓存,可以监听下载进度,完成后可以在block中做事情
 * @param url           下载图片路径
 * @param placeholder   占位图片,直到下载完成才替换
 * @param options       下载图片选择方式
 * @param progress
 * SDWebImageDownloaderProgressBlock:NSInteger receivedSize(当前下载大小), NSInteger expectedSize(总大小)
 * @param completedBlock  同上
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

/**
 * 异步下载图片并缓存,可以监听下载进度,完成后可以在block中做事情
 * @param url           下载图片路径
 * @param placeholder   占位图片,直到下载完成才替换
 * @param options       下载图片选择方式
 * @param progress
 * SDWebImageDownloaderProgressBlock:NSInteger receivedSize(当前下载大小), NSInteger expectedSize(总大小)
 * @param completedBlock  同上
 */
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;


SDWebImageOptions中的选项对应


 //失败后重新下载
    SDWebImageRetryFailed = 1 << 0,

    //最低优先级,当正在进行UI交互时,自动暂停内部的一些下载操作
    SDWebImageLowPriority = 1 << 1,

    //只缓存内存
    SDWebImageCacheMemoryOnly = 1 << 2,

    //渐进式下载,显示的图像是逐步在下载
    SDWebImageProgressiveDownload = 1 << 3,

    //刷新缓存
    SDWebImageRefreshCached = 1 << 4,

    //后台下载
    SDWebImageContinueInBackground = 1 << 5,

    /**
     * Handles cookies stored in NSHTTPCookieStore by setting
     * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
     */
    SDWebImageHandleCookies = 1 << 6,

    //允许使用无效的SSL证书
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

    //高优先级下载
    SDWebImageHighPriority = 1 << 8,
    
    //延迟占位符
    SDWebImageDelayPlaceholder = 1 << 9,

    //改变动画形象
    SDWebImageTransformAnimatedImage = 1 << 10,
    
    /**
     * By default, image is added to the imageView after download. But in some cases, we want to
     * have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
     * Use this flag if you want to manually set the image in the completion when success
     */
    SDWebImageAvoidAutoSetImage = 1 << 11

设置图片另外几种方法

##常用
 /*
     第一个参数:要下载图片的url地址
     第二个参数:设置该imageView的占位图片
     第三个参数:传一个枚举值,告诉程序你下载图片的策略是什么
     第一个block块:获取当前图片数据的下载进度
     receivedSize:已经下载完成的数据大小
     expectedSize:该文件的数据总大小
     第二个block块:当图片下载完成之后执行该block中的代码
     image:下载得到的图片数据
     error:下载出现的错误信息
     SDImageCacheType:图片的缓存策略(不缓存,内存缓存,沙盒缓存)
     imageURL:下载的图片的url地址
     */
    [self.imageView sd_setImageWithURL:
                 [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201409/02/20140902150244_nMMEj.jpeg"]
                      placeholderImage:[UIImage imageNamed:@"Snip20160111_304"]
                               options:SDWebImageRetryFailed|SDWebImageLowPriority 
                              progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        NSLog(@"%f",1.0 * receivedSize/expectedSize);
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        
                            switch (cacheType) {
                            case SDImageCacheTypeNone:
                                NSLog(@"没有使用缓存,图片是直接下载的");
                                break;
                            case SDImageCacheTypeDisk:
                                NSLog(@"磁盘缓存");
                                break;
                            case SDImageCacheTypeMemory:
                                NSLog(@"内存缓存");
                                break;
                            default:
                                break;
                            }
    }];


##//如果不需要占位图片,可以用如下代码


 [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201409/02/20140902150244_nMMEj.jpeg"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
         NSLog(@"%f",1.0 * receivedSize/expectedSize);
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        self.imageView.image = image;
        switch (cacheType) {
            case SDImageCacheTypeNone:
                NSLog(@"没有使用缓存,图片是直接下载的");
                break;
            case SDImageCacheTypeDisk:
                NSLog(@"磁盘缓存");
                break;
            case SDImageCacheTypeMemory:
                NSLog(@"内存缓存");
                break;
            default:
                break;
        }
    }];

##//内部并不会做缓存处理
##//注意点:completed回调是在子线程处理的
/

    [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:@"http://img.qqbody.com/uploads/allimg/201503/20150331222879.gif"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        NSLog(@"%f",1.0 * receivedSize/expectedSize);

    } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {

        //线程间通信
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
              self.imageView.image = [UIImage sd_animatedGIFWithData:data];
        }];
    }];

系统级的内存警告如何处理

-(void)applicationDidReceiveMemoryWarning:(UIApplication *)application{
    
    //取消当前正在进行的所有下载操作
    [[SDWebImageManager sharedManager] cancelAll];
    
    //清除缓存数据
    //cleanDisk:删除过期的文件数据,计算当前未过期的已经下载的文件数据的大小,
//    如果发现该数据大小大于我们设置的最大缓存数据大小,那么程序内部会按照按文件数据缓存的时间从远到近删除,知道小于最大缓存数据为止。
    [[SDWebImageManager sharedManager].imageCache cleanDisk];
    
    //clearMemory:直接删除文件,重新创建新的文件夹
    [[SDWebImageManager sharedManager].imageCache clearMemory];

}

SDWebImage原理分析与常识

 - SDWebImage的磁盘缓存, 是按照时间来处理的, 只要缓存数据超过了最大的缓存时间, 就会自动删除
     - SDWebImage默认的磁盘缓存时间是多久? 
        + 1周
     - SDWebImage接收到内存警告会如何处理
        + 只要接收到内存警告就会调用 clearMemory 清空内存缓存
     - SDWebImage即将要被终结如何处理
        + 会调用 cleanDisk 方法, 删除过期的文件
     - SDWebImage存储到什么为止
        + caches文件夹下面
        + 新建一个default文件夹用于缓存
     - SDWebImage是如何清空缓存 ?
        + clearMemory
        + 移除NSCache中保存的所有图片对象

     - SDWebImage是如何清除磁盘
        + cleanDisk : 清除过期的
            * 遍历缓存目录, 找到所有过期的文件, 并删除
            * 查看当maxCacheSize的值, 如果删除之后缓存的大小, 还大于maxCacheSize, 那么就会从时间较早的开始继续删除, 直到缓存大小小于maxCacheSize为止
        + clearDisk : 清除所有
            * 直接干掉缓存文件夹
            * 重新创建一个新的文件夹, 作为缓存文件

     - SDWebImage可以直接播放GIF图片
        + 加载GIF图片, 然后取出GIF图片中所有的帧, 并且计算动画时间
        + 根据取出的帧和动画时间生产一张新的可动画的图片

     - SDWebImage它可以判断图片的类型
        + 图片的十六进制数据, 的前8个字节都是一样的, 所以可以同判断十六进制来判断图片的类型
        + PNG
        + JPG
        + ...

1.SDWebImage相关知识点补充

  1.SDWebImage接收到内存警告的时候如何处理?采用监听系统警告通知的方式处理,接收到警告后清空缓存
  2.SDWebImage队列最大并发数为6
  3.SDWebImage内部设置下载图片超时时间为15m
  4.SDWebImage图片下载操作使用了NSURLConnection类发送网络请求实现
  5.SDWebImage内部使用NSCache类来进行缓存处理
  6.SDWebImage内部如何判断图片类型?判断该图片二进制数据的第一个字节
  7.沙盒缓存图片的命名方式为对该图片的URL进行MD5加密  (Mac终端输入:echo -n "url" |MD5 可获得MD5密文)
  8. 图片的下载顺序,默认是先进先出的

2.NSCache知识点补充

 1.NSCache是专门用来进行缓存处理的,
 2.NSCache简单介绍:
    2-1 NSCache是苹果官方提供的缓存类,具体使用和NSMutableDictionary类似,在AFN和SDWebImage框架中被使用来管理缓存
    2-2 苹果官方解释NSCache在系统内存很低时,会自动释放对象(但模拟器演示不会释放)
        建议:接收到内存警告时主动调用removeAllObject方法释放对象
    2-3 NSCache是线程安全的,在多线程操作中,不需要对NSCache加锁
    2-4 NSCache的Key只是对对象进行Strong引用,不是拷贝
 3 属性介绍:
    name:名称
    delegete:设置代理
    totalCostLimit:缓存空间的最大总成本,超出上限会自动回收对象。默认值为0,表示没有限制
    countLimit:能够缓存的对象的最大数量。默认值为0,表示没有限制
    evictsObjectsWithDiscardedContent:标识缓存是否回收废弃的内容
  4 方法介绍
- (void)setObject:(ObjectType)obj forKey:(KeyType)key;//在缓存中设置指定键名对应的值,0成本
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;//在缓存中设置指定键名对应的值,并且指定该键值对的成本,用于计算记录在缓存中的所有对象的总成本,出现内存警告或者超出缓存总成本上限的时候,缓存会开启一个回收过程,删除部分元素
- (void)removeObjectForKey:(KeyType)key;//删除缓存中指定键名的对象
- (void)removeAllObjects;//删除缓存中所有的对象

参考资料
南峰子的技术博客
iOS开发实践之cell下载图片(SDWebImage)
SDWebImage框架底层讲解


随手点个喜欢吧~

关注我

QQ--iOS 交流群:107548668

相关文章

网友评论

本文标题:解析iOS开源项目--SDWebImage

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