美文网首页
SDWebImage前世今生之V3.0版本

SDWebImage前世今生之V3.0版本

作者: _相信自己_ | 来源:发表于2019-03-06 02:10 被阅读0次

    3.0版本2012年11月30日发布

    3.0版本SDWevbImage正式进入成熟阶段,整体结构优化改进,代码结构优化突出,GCD的到来、block全面支持、ARC自动引用计数。
    主要更新:

    • SDWebImageDownloaderDelegate
      下载器代理废弃,彻底移除使用block替代
    • SDWebImageManagerDelegate
      管理器代理废弃,彻底移除使用block替代
    • SDImageCacheDelegate
      缓存器代理废弃,彻底移除使用block替代
    • 新增SDWebImageOperation协议
      下载操作代理
    • 新增SDWebImageDownloaderOperation
      将下载器当中的网络功能进行重构

    1. UIImageView+WebCache

    @interface UIImageView (WebCache)
    
    - (void)setImageWithURL:(NSURL *)url;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock;
    //-------------------------------end-------------------------------
    
    - (void)cancelCurrentImageLoad;
    
    @end
    
    static char operationKey;
    
    @implementation UIImageView (WebCache)
    
    - (void)setImageWithURL:(NSURL *)url{
        [self setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder{
        [self setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options{
        [self setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
    }
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock{
        [self setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock{
        [self setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock{
        [self setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock;{
        [self cancelCurrentImageLoad];
        self.image = placeholder;
        if (url){
            __weak UIImageView *wself = self;
            id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished){
                __strong UIImageView *sself = wself;
                if (!sself) return;
                if (image){
                    sself.image = image;
                    [sself setNeedsLayout];
                }
                if (completedBlock && finished){
                    completedBlock(image, error, cacheType);
                }
            }];
            objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    - (void)cancelCurrentImageLoad{
        //从队列中取消正在进行的下载加载程序
        id<SDWebImageOperation> operation = objc_getAssociatedObject(self, &operationKey);
        if (operation){
            [operation cancel];
            objc_setAssociatedObject(self, &operationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    //-------------------------------end-------------------------------
    
    @end
    

    2. UIButton+WebCache

    @interface UIButton (WebCache)
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state;
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
    //-------------------------------end-------------------------------
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock;
    //-------------------------------end-------------------------------
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state;
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
    //-------------------------------end-------------------------------
    
    //------------------------3.0版本更新-功能优化------------------------
    //备注:block支持
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock;
    //-------------------------------end-------------------------------
    
    - (void)cancelCurrentImageLoad;
    
    @end
    
    // 分类属性
    static char operationKey;
    
    @implementation UIButton (WebCache)
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state{
        [self setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
    }
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder{
        [self setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
    }
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options{
        [self setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
    }
    //-------------------------------end-------------------------------
    
    //------------------------3.0版本更新-block支持------------------------
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock{
        [self setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
    }
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock{
        [self setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
    }
    - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock{
        [self cancelCurrentImageLoad];
        [self setImage:placeholder forState:state];
        if (url){
            __weak UIButton *wself = self;
            id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished){
                __strong UIButton *sself = wself;
                if (!sself) return;
                if (image){
                    [sself setImage:image forState:state];
                }
                if (completedBlock && finished){
                    completedBlock(image, error, cacheType);
                }
            }];
            objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    //-------------------------------end-------------------------------
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state{
        [self setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
    }
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder{
        [self setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
    }
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options{
        [self setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
    }
    //-------------------------------end-------------------------------
    
    //------------------------3.0版本更新-block支持------------------------
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock{
        [self setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
    }
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock{
        [self setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
    }
    - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock{
        [self cancelCurrentImageLoad];
        [self setBackgroundImage:placeholder forState:state];
        if (url){
            __weak UIButton *wself = self;
            id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished){
                __strong UIButton *sself = wself;
                if (!sself) return;
                if (image){
                    [sself setBackgroundImage:image forState:state];
                }
                if (completedBlock && finished){
                    completedBlock(image, error, cacheType);
                }
            }];
            // 关联对象
            objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    //-------------------------------end-------------------------------
    
    //------------------------3.0版本更新-功能优化------------------------
    - (void)cancelCurrentImageLoad{
        //从队列中取消正在进行的下载加载程序
        id<SDWebImageOperation> operation = objc_getAssociatedObject(self, &operationKey);
        if (operation){
            [operation cancel];
            // 移除对象关联
            objc_setAssociatedObject(self, &operationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    //-------------------------------end-------------------------------
    
    @end
    

    3. MKAnnotationView+WebCache

    @interface MKAnnotationView (WebCache)
    - (void)setImageWithURL:(NSURL *)url;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
    
    //----------------------3.0版本更新-去掉了block兼容宏定义---------------------
    - (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock;
    - (void)cancelCurrentImageLoad;
    //-----------------------------------end----------------------------------
    
    @end
    
    static char operationKey;
    
    @implementation MKAnnotationView (WebCache)
    
    - (void)setImageWithURL:(NSURL *)url{
        [self setImageWithURL:url placeholderImage:nil options:0 completed:nil];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder{
        [self setImageWithURL:url placeholderImage:placeholder options:0 completed:nil];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options{
        [self setImageWithURL:url placeholderImage:placeholder options:options completed:nil];
    }
    
    
    //-----------------------3.0版本更新-去掉了block兼容宏定义----------------------
    - (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock{
        [self setImageWithURL:url placeholderImage:nil options:0 completed:completedBlock];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock{
        [self setImageWithURL:url placeholderImage:placeholder options:0 completed:completedBlock];
    }
    - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock{
        [self cancelCurrentImageLoad];
        self.image = placeholder;
        if (url){
            __weak MKAnnotationView *wself = self;
            id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, BOOL fromCache, BOOL finished){
                __strong MKAnnotationView *sself = wself;
                if (!sself) return;
                if (image)
                {
                    sself.image = image;
                }
                if (completedBlock && finished)
                {
                    completedBlock(image, error, fromCache);
                }
            }];
            objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    //-----------------------------------end----------------------------------
    
    - (void)cancelCurrentImageLoad{
        //从队列中取消正在进行的下载加载程序
        id<SDWebImageOperation> operation = objc_getAssociatedObject(self, &operationKey);
        if (operation){
            [operation cancel];
            objc_setAssociatedObject(self, &operationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    
    @end
    

    4. SDWebImageManager

    • 废除了block兼容代码
    • 下载方法集成
      由原来的2.7版本的8个下载方法集成到了一个整体类功能结构大大简化了复杂度(封装更加完善)废除了很多冗余代码
    typedef enum{
        //如果下载失败,还会继续尝试下载
        SDWebImageRetryFailed = 1 << 0,
        //滑动的时候Scrollview不下载,手从屏幕上移走,Scrollview开始减速的时候才会开始下载图片
        SDWebImageLowPriority = 1 << 1,
        //禁止磁盘缓存,只有内存缓存
        SDWebImageCacheMemoryOnly = 1 << 2,
        //正常情况图片下载完成,然后一次性显示,设置该选项那么图片边下载边显示
        SDWebImageProgressiveDownload = 1 << 3
    } SDWebImageOptions;
    
    //------------------3.0版本更新-去除兼容宏定义block----------------------
    typedef void(^SDWebImageCompletedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType);
    typedef void(^SDWebImageCompletedWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished);
    //----------------------------end-------------------------------
    
    @interface SDWebImageManager : NSObject
    + (SDWebImageManager *)sharedManager;
    
    //------------------3.0版本更新-功能更新--------------------------
    @property (strong, nonatomic, readonly) SDImageCache *imageCache;
    @property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
    @property (strong) NSString *(^cacheKeyFilter)(NSURL *url);
    - (id<SDWebImageOperation>)downloadWithURL:(NSURL *)url
                                       options:(SDWebImageOptions)options
                                      progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                     completed:(SDWebImageCompletedWithFinishedBlock)completedBlock;
    //----------------------------end-------------------------------
    - (void)cancelAll;
    
    @end
    
    //------------------3.0版本更新-功能更新--------------------------
    @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
    @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
    @property (copy, nonatomic) void (^cancelBlock)(void);
    @end
    
    @implementation SDWebImageCombinedOperation
    - (void)setCancelBlock:(void (^)(void))cancelBlock{
        if (self.isCancelled){
            if (cancelBlock) cancelBlock();
        } else{
            _cancelBlock = [cancelBlock copy];
        }
    }
    - (void)cancel{
        self.cancelled = YES;
        if (self.cancelBlock){
            self.cancelBlock();
            self.cancelBlock = nil;
        }
    }
    @end
    
    //备注:外观模式标准写法
    @interface SDWebImageManager ()
    @property (strong, nonatomic, readwrite) SDImageCache *imageCache;
    @property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;
    @property (strong, nonatomic) NSMutableArray *failedURLs;
    @property (strong, nonatomic) NSMutableArray *runningOperations;
    @end
    //----------------------------end------------------------------
    
    
    @implementation SDWebImageManager
    
    //-------------------3.0版本更新-GCD----------------------------
    + (id)sharedManager{
        static dispatch_once_t once;
        static id instance;
        dispatch_once(&once, ^{
            instance = self.new;
        });
        return instance;
    }
    //----------------------------end------------------------------
    
    - (instancetype)init{
        self = [super init];
        if (self) {
            _imageCache = SDImageCache.new;
            _imageDownloader = SDWebImageDownloader.new;
            _failedURLs = NSMutableArray.new;
            _runningOperations = NSMutableArray.new;
        }
        return self;
    }
    
    - (NSString *)cacheKeyForURL:(NSURL *)url{
        if (self.cacheKeyFilter){
            return self.cacheKeyFilter(url);
        } else {
            return [url absoluteString];
        }
    }
    
    //-------------------3.0版本更新-功能更新----------------------------
    - (id<SDWebImageOperation>)downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock{
        //非常常见的错误是使用NSString对象而不是NSURL发送URL。出于某种奇怪的原因,XCode不会这样做
        //为这种类型不匹配抛出任何警告。在这里,我们通过允许url作为NSString传递来防止这个错误
        if ([url isKindOfClass:NSString.class]){
            url = [NSURL URLWithString:(NSString *)url];
        }
        //防止应用程序在参数类型错误上崩溃,比如发送NSNull而不是NSURL
        if (![url isKindOfClass:NSURL.class]){
            url = nil;
        }
        __block SDWebImageCombinedOperation *operation = SDWebImageCombinedOperation.new;
        if (!url || !completedBlock || (!(options & SDWebImageRetryFailed) && [self.failedURLs containsObject:url])){
            if (completedBlock) completedBlock(nil, nil, SDImageCacheTypeNone, NO);
            return operation;
        }
        [self.runningOperations addObject:operation];
        NSString *key = [self cacheKeyForURL:url];
        [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType){
            if (operation.isCancelled) return;
            if (image){
                completedBlock(image, nil, cacheType, YES);
                [self.runningOperations removeObject:operation];
            } else {
                SDWebImageDownloaderOptions downloaderOptions = 0;
                if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
                if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
                __block id<SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished){
                    completedBlock(downloadedImage, error, SDImageCacheTypeNone, finished);
                    if (error){
                        [self.failedURLs addObject:url];
                    } else if (downloadedImage && finished) {
                        [self.imageCache storeImage:downloadedImage imageData:data forKey:key toDisk:YES];
                    }
                    if (finished){
                        [self.runningOperations removeObject:operation];
                    }
                }];
                operation.cancelBlock = ^{[subOperation cancel];};
            }
        }];
    
        return operation;
    }
    - (void)cancelAll{
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.runningOperations makeObjectsPerformSelector:@selector(cancel)];
            [self.runningOperations removeAllObjects];
        });
    }
    //----------------------------end------------------------------
    
    @end
    

    5. SDWebImageDownloader

    • 新增下载模式(抽象为枚举定义)
      枚举:SDWebImageDownloaderOptions
      模式一:下载失败,重复下载
      模式二:渐进下载模式
    • 新增正在下载block
      成功下载block、正在下载block、新增下载进度
    • 下载器下载方法集成
      由原来的2.7版本3个下载方法集成为一个
    extern NSString *const SDWebImageDownloadStartNotification;
    extern NSString *const SDWebImageDownloadStopNotification;
    
    //-----------------------3.0版本更新-功能优化-----------------------
    typedef enum{
        SDWebImageDownloaderLowPriority = 1 << 0,
        SDWebImageDownloaderProgressiveDownload = 1 << 1
    } SDWebImageDownloaderOptions;
    //-----------------------------end-------------------------------
    
    //-----------------------3.0版本更新-block回调---------------------
    typedef void(^SDWebImageDownloaderProgressBlock)(NSUInteger receivedSize, long long expectedSize);
    typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
    //-------------------------------end--------------------------------
    
    //异步下载加载专用和优化的图像加载
    @interface SDWebImageDownloader : NSObject
    
    @property (assign, nonatomic) NSInteger maxConcurrentDownloads;
    
    + (SDWebImageDownloader *)sharedDownloader;
    
    //---------------------------3.0版本更新-优化-------------------------
    - (id<SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                            options:(SDWebImageDownloaderOptions)options
                                           progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                          completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
    //-------------------------------end--------------------------------
    @end
    
    NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
    NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
    
    //-----------------------3.0版本更新-功能优化-----------------------
    static NSString *const kProgressCallbackKey = @"progress";
    static NSString *const kCompletedCallbackKey = @"completed";
    //-----------------------------end-------------------------------
    
    //-----------------------3.0版本更新-功能优化-----------------------
    @interface SDWebImageDownloader ()
    @property (strong, nonatomic) NSOperationQueue *downloadQueue;
    @property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
    //此队列用于序列化单个队列中所有下载操作的网络响应的处理
    @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t workingQueue;
    @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
    @end
    //-----------------------------end-------------------------------
    
    //-----------------------3.0版本更新-功能优化-----------------------
    @implementation SDWebImageDownloader
    + (void)initialize{
        //绑定SDNetworkActivityIndicator(下载地址:http://github.com/rs/SDNetworkActivityIndicator)
        //要使用它,只需添加#import "SDNetworkActivityIndicator。除了SDWebImage的导入
        if (NSClassFromString(@"SDNetworkActivityIndicator")){
            //去除警告
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
    #pragma clang diagnostic pop
    
            //删除观察员以防它之前添加
            [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
            [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
            [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                selector:NSSelectorFromString(@"startActivity")
                                                         name:SDWebImageDownloadStartNotification object:nil];
            [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                selector:NSSelectorFromString(@"stopActivity")
                                                         name:SDWebImageDownloadStopNotification object:nil];
        }
    }
    
    + (SDWebImageDownloader *)sharedDownloader{
        static dispatch_once_t once;
        static id instance;
        dispatch_once(&once, ^{
            instance = self.new;
        });
        return instance;
    }
    
    - (instancetype)init{
        self = [super init];
        if (self) {
            _downloadQueue = NSOperationQueue.new;
            _downloadQueue.maxConcurrentOperationCount = 2;
            _URLCallbacks = NSMutableDictionary.new;
            _workingQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloader", DISPATCH_QUEUE_SERIAL);
            _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        }
        return self;
    }
    
    - (void)dealloc{
        [self.downloadQueue cancelAllOperations];
        SDDispatchQueueRelease(_workingQueue);
        SDDispatchQueueRelease(_barrierQueue);
    }
    
    - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads{
        _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
    }
    - (NSInteger)maxConcurrentDownloads{
        return _downloadQueue.maxConcurrentOperationCount;
    }
    
    - (id<SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSUInteger, long long))progressBlock completed:(void (^)(UIImage *, NSData *, NSError *, BOOL))completedBlock{
        __block SDWebImageDownloaderOperation *operation;
        __weak SDWebImageDownloader *wself = self;
        [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{
            //为了防止潜在的重复缓存(NSURLCache + SDImageCache),我们为图像请求禁用缓存
            NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:15];
            request.HTTPShouldHandleCookies = NO;
            request.HTTPShouldUsePipelining = YES;
            [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
            operation = [SDWebImageDownloaderOperation.alloc initWithRequest:request queue:self.workingQueue options:options progress:^(NSUInteger receivedSize, long long expectedSize){
                if (!wself) return;
                SDWebImageDownloader *sself = wself;
                NSArray *callbacksForURL = [sself callbacksForURL:url remove:NO];
                for (NSDictionary *callbacks in callbacksForURL){
                    SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                    if (callback) callback(receivedSize, expectedSize);
                }
            }
            completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished){
                if (!wself) return;
                SDWebImageDownloader *sself = wself;
                NSArray *callbacksForURL = [sself callbacksForURL:url remove:finished];
                for (NSDictionary *callbacks in callbacksForURL)
                {
                    SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                    if (callback) callback(image, data, error, finished);
                }
            }
            cancelled:^{
                if (!wself) return;
                SDWebImageDownloader *sself = wself;
                [sself callbacksForURL:url remove:YES];
            }];
            [self.downloadQueue addOperation:operation];
        }];
        return operation;
    }
    
    - (void)addProgressCallback:(void (^)(NSUInteger, long long))progressBlock andCompletedBlock:(void (^)(UIImage *, NSData *data, NSError *, BOOL))completedBlock forURL:(NSURL *)url createCallback:(void (^)())createCallback{
        dispatch_barrier_sync(self.barrierQueue, ^{
            BOOL first = NO;
            if (!self.URLCallbacks[url]){
                self.URLCallbacks[url] = NSMutableArray.new;
                first = YES;
            }
            //处理同一URL同时下载请求的单次下载
            NSMutableArray *callbacksForURL = self.URLCallbacks[url];
            NSMutableDictionary *callbacks = NSMutableDictionary.new;
            if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
            if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
            [callbacksForURL addObject:callbacks];
            self.URLCallbacks[url] = callbacksForURL;
            if (first){
                createCallback();
            }
        });
    }
    - (NSArray *)callbacksForURL:(NSURL *)url remove:(BOOL)remove{
        __block NSArray *callbacksForURL;
        if (remove){
            dispatch_barrier_sync(self.barrierQueue, ^{
                callbacksForURL = self.URLCallbacks[url];
                [self.URLCallbacks removeObjectForKey:url];
            });
        } else {
            dispatch_sync(self.barrierQueue, ^{
                callbacksForURL = self.URLCallbacks[url];
            });
        }
        return callbacksForURL;
    }
    
    @end
    //-----------------------------end-------------------------------
    

    6. SDImageCache

    • 下载回调block方式
      由原来的2.7版本的delegate方式优化为block方式
    • 移除部分磁盘操作
      例如:删除了setMaxCacheAge(替换为属性)、getMemorySize、getMemoryCount方法
    • 所有的get、set方法的配置都废除,采用系统提供默认get、set方法
      属性替代了:maxCacheAge
    • 新增缓存初始化方法
      初始化具有特定名称空间的新缓存存储
      - (id)initWithNamespace:(NSString *)ns;
      做了一些处理
      UIApplicationDidReceiveMemoryWarningNotification:内存警告通知
      应用在前台双击Home键终止应用调用UIApplicationWillTerminateNotification
      应用在前台单击Home键进入桌面再终止应用UIApplicationWillTerminateNotification不会被调用
    • 采用了单例模式->GCD方式->性能优化
      SDWebImage3.0时代开启了GCD应用了
    • 开启了异步代码->GCD方式->dispatch_async->性能优化
    enum SDImageCacheType{
        //SDWebImage缓存无法使用该图像,但已从web下载
        SDImageCacheTypeNone = 0,
        //图像是从磁盘缓存中获得的
        SDImageCacheTypeDisk,
        //图像是从磁盘缓存中获得的
        SDImageCacheTypeMemory
    };
    typedef enum SDImageCacheType SDImageCacheType;
    
    
    @interface SDImageCache : NSObject
    
    //返回全局共享缓存实例
    + (SDImageCache *)sharedImageCache;
    
    //将图片存储到给定键上的内存和磁盘缓存中
    - (void)storeImage:(UIImage *)image forKey:(NSString *)key;
    //将图片存储到给定键上的内存和可选磁盘缓存中
    - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
    //将图片存储到给定键上的内存和可选磁盘缓存中
    - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk;
    
    //同步地从内存和磁盘缓存中删除图片
    - (void)removeImageForKey:(NSString *)key;
    //同步地从内存和磁盘缓存中删除图像
    - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
    
    //清除所有内存缓存图片
    - (void)clearMemory;
    //清除所有磁盘缓存的图片
    - (void)clearDisk;
    //从磁盘删除所有过期的缓存图片
    - (void)cleanDisk;
    
    //获取磁盘缓存使用的大小
    - (int)getSize;
    
    //-------------3.0版本更新-新增功能->异步方法->block方式----------------
    //异步查询磁盘缓存
    - (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDImageCacheType cacheType))doneBlock;
    //---------------------------------end-----------------------------
    
    //---------------3.0版本更新-功能更新--------------------
    //备注:删除了setMaxCacheAge(替换为属性)、getMemorySize、getMemoryCount方法
    //获取磁盘缓存中的图片数
    - (int)getDiskCount;
    //在缓存中保存图像的最长时间,以秒为单位
    @property (assign, nonatomic) NSInteger maxCacheAge;
    //初始化具有特定名称空间的新缓存存储
    - (id)initWithNamespace:(NSString *)ns;
    //---------------------------end----------------------
    
    @end
    
    @implementation SDImageCache
    
    //---------------3.0版本更新-功能更新-GCD更新--------------------
    + (SDImageCache *)sharedImageCache{
        static dispatch_once_t once;
        static id instance;
        dispatch_once(&once, ^{
            instance = self.new;
        });
        return instance;
    }
    //---------------------------end----------------------
    
    //---------------3.0版本更新-功能更新--------------------
    //初始化具有特定名称空间的新缓存存储
    - (id)initWithNamespace:(NSString *)ns{
        if ((self = [super init])){
            NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
            //创建IO串行队列
            _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
            //初始化默认值
            _maxCacheAge = kDefaultCacheMaxCacheAge;
            //初始化内存缓存
            _memCache = [[NSCache alloc] init];
            _memCache.name = fullNamespace;
            //初始化磁盘缓存
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
            _diskCachePath = [paths[0] stringByAppendingPathComponent:fullNamespace];
    
            //---------------3.0版本更新-功能更新-兼容iPhone手机-------------------
    #if TARGET_OS_IPHONE
            //订阅应用程序事件
            //UIApplicationDidReceiveMemoryWarningNotification:内存警告通知
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(clearMemory)
                                            name:UIApplicationDidReceiveMemoryWarningNotification
                                                       object:nil];
            //应用在前台双击Home键终止应用调用UIApplicationWillTerminateNotification
            //应用在前台单击Home键进入桌面再终止应用UIApplicationWillTerminateNotification不会被调用
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(cleanDisk)
                                            name:UIApplicationWillTerminateNotification
                                                       object:nil];
    #endif
            //-------------------------------end-------------------------------
        }
        return self;
    }
    //---------------------------end----------------------
    
    
    - (void)dealloc{
        [[NSNotificationCenter defaultCenter] removeObserver:self];
        SDDispatchQueueRelease(_ioQueue);
    }
    
    - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk{
        if (!image || !key){
            return;
        }
        //-----------------------3.0版本更新-功能优化-----------------------
        [self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale];
        if (toDisk){
            dispatch_async(self.ioQueue, ^{
                NSData *data = imageData;
                if (!data){
                    if (image){
    #if TARGET_OS_IPHONE
                        data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
    #else
                        data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
    #endif
                    }
                }
                if (data){
                    //不能使用defaultManager另一个线程
                    NSFileManager *fileManager = NSFileManager.new;
                    if (![fileManager fileExistsAtPath:self->_diskCachePath]){
                        [fileManager createDirectoryAtPath:self->_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
                    }
                    [fileManager createFileAtPath:[self cachePathForKey:key] contents:data attributes:nil];
                }
            });
        }
        //------------------------------end------------------------------
        //------------------------------end------------------------------
    }
    
    - (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDImageCacheType cacheType))doneBlock{
        //---------------3.0版本更新-优化--------------------
        if (!doneBlock)
            return;
        if (!key){
            doneBlock(nil, SDImageCacheTypeNone);
            return;
        }
        //首先检查内存缓存…
        UIImage *image = [self.memCache objectForKey:key];
        if (image){
            doneBlock(image, SDImageCacheTypeMemory);
            return;
        }
        dispatch_async(self.ioQueue, ^{
            UIImage *diskImage = [UIImage decodedImageWithImage:SDScaledImageForPath(key, [NSData dataWithContentsOfFile:[self cachePathForKey:key]])];
            if (diskImage){
                [self.memCache setObject:diskImage forKey:key cost:image.size.height * image.size.width * image.scale];
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, SDImageCacheTypeDisk);
            });
        });
        //------------------------end-----------------------
    }
    
    - (void)removeImageForKey:(NSString *)key{
        [self removeImageForKey:key fromDisk:YES];
    }
    //-----------------------2.6版本更新-方法重载---------------------
    - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk{
        if (key == nil){
            return;
        }
        [self.memCache removeObjectForKey:key];
        if (fromDisk){
            //-----------------------3.0版本更新-性能优化---------------------
            dispatch_async(self.ioQueue, ^{
                [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil];
            });
            //---------------------------end----------------------------
        }
    }
    //-----------------------------end------------------------------
    
    //--------------------3.0版本更新-性能优化-异步清理------------------
    - (void)clearDisk{
        dispatch_async(self.ioQueue, ^{
            [[NSFileManager defaultManager] removeItemAtPath:self.diskCachePath error:nil];
            [[NSFileManager defaultManager] createDirectoryAtPath:self.diskCachePath
                                      withIntermediateDirectories:YES
                                                       attributes:nil
                                                            error:NULL];
        });
    }
    - (void)cleanDisk{
        dispatch_async(self.ioQueue, ^{
            NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
            NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:self.diskCachePath];
            for (NSString *fileName in fileEnumerator){
                NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
                NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
                if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate])
                {
                    [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
                }
            }
        });
    }
    //------------------------------end------------------------------
    
    @end
    

    7. SDWebImageDecoder

    • 移除SDWebImageDecoderDelegateSDWebImageDecoder
    @interface UIImage (ForceDecode)
    
    + (UIImage *)decodedImageWithImage:(UIImage *)image;
    
    @end
    
    @implementation UIImage (ForceDecode)
    
    + (UIImage *)decodedImageWithImage:(UIImage *)image{
        CGImageRef imageRef = image.CGImage;
        CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
        CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize};
    
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(NULL, imageSize.width, imageSize.height, CGImageGetBitsPerComponent(imageRef), CGImageGetBytesPerRow(imageRef), colorSpace, CGImageGetBitmapInfo(imageRef));
        CGColorSpaceRelease(colorSpace);
        //如果失败,返回未解压缩的图片
        if (!context)
            return image;
        
        CGContextDrawImage(context, imageRect, imageRef);
        CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
        CGContextRelease(context);
        
        UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef];
        CGImageRelease(decompressedImageRef);
        return decompressedImage;
    }
    
    @end
    

    8. SDWebImagePrefetcher

    //在缓存中预取一些url以便将来使用,下载图像的优先级很低
    @interface SDWebImagePrefetcher : NSObject
    
    //同时预取的最大url数,默认为3
    @property (nonatomic, assign) NSUInteger maxConcurrentDownloads;
    //SDWebImageOptions prefetcher。默认为SDWebImageLowPriority
    @property (nonatomic, assign) SDWebImageOptions options;
    
    + (SDWebImagePrefetcher *)sharedImagePrefetcher;
    
    //分配url列表,让SDWebImagePrefetcher对预取队列,目前一次下载一张图片
    //然后跳过下载失败的图片,进入列表中的下一个图片
    - (void)prefetchURLs:(NSArray *)urls;
    
    //删除并取消排队列表
    - (void)cancelPrefetching;
    
    @end
    
    @interface SDWebImagePrefetcher ()
    @property (strong, nonatomic) SDWebImageManager *manager;
    @property (strong, nonatomic) NSArray *prefetchURLs;
    @property (assign, nonatomic) NSUInteger requestedCount;
    @property (assign, nonatomic) NSUInteger skippedCount;
    @property (assign, nonatomic) NSUInteger finishedCount;
    @property (assign, nonatomic) NSTimeInterval startedTime;
    @end
    
    @implementation SDWebImagePrefetcher
    
    + (SDWebImagePrefetcher *)sharedImagePrefetcher{
        static dispatch_once_t once;
        static id instance;
        dispatch_once(&once, ^{
            instance = self.new;
        });
        return instance;
    }
    
    - (instancetype)init{
        self = [super init];
        if (self) {
            _manager = SDWebImageManager.new;
            _options = SDWebImageLowPriority;
            self.maxConcurrentDownloads = 3;
        }
        return self;
    }
    
    - (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads{
        self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads;
    }
    
    - (NSUInteger)maxConcurrentDownloads{
        return self.manager.imageDownloader.maxConcurrentDownloads;
    }
    
    - (void)startPrefetchingAtIndex:(NSUInteger)index{
        if (index >= self.prefetchURLs.count) return;
        self.requestedCount++;
        [self.manager downloadWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished){
            if (!finished) return;
            self.finishedCount++;
            if (image){
                NSLog(@"Prefetched %lu out of %lu", (unsigned long)self.finishedCount, (unsigned long)self.prefetchURLs.count);
            } else {
                NSLog(@"Prefetched %lu out of %lu (Failed)", (unsigned long)self.finishedCount, (unsigned long)[self.prefetchURLs count]);
                self.skippedCount++;
            }
            if (self.prefetchURLs.count > self.requestedCount){
                [self startPrefetchingAtIndex:self.requestedCount];
            } else if (self.finishedCount == self.requestedCount){
                [self reportStatus];
            }
        }];
    }
    
    - (void)reportStatus{
        NSUInteger total = [self.prefetchURLs count];
        NSLog(@"Finished prefetching (%lu successful, %lu skipped, timeElasped %.2f)", total - self.skippedCount, (unsigned long)self.skippedCount, CFAbsoluteTimeGetCurrent() - self.startedTime);
    }
    
    - (void)prefetchURLs:(NSArray *)urls{
        //防止重复预取请求
        [self cancelPrefetching];
        self.startedTime = CFAbsoluteTimeGetCurrent();
        self.prefetchURLs = urls;
        //从列表中第一个图像开始预取,最大允许并发性
        NSUInteger listCount = self.prefetchURLs.count;
        for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++){
            [self startPrefetchingAtIndex:i];
        }
    }
    
    - (void)cancelPrefetching{
        self.prefetchURLs = nil;
        self.skippedCount = 0;
        self.requestedCount = 0;
        self.finishedCount = 0;
        [self.manager cancelAll];
    }
    
    @end
    

    9. SDWebImageCompat

    //-------------------3.0版本更新-新增平台兼容-------------------
    #ifdef __OBJC_GC__
    #error SDWebImage does not support Objective-C Garbage Collection
    #endif
    
    #if !__has_feature(objc_arc)
    #error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
    #endif
    
    #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
    #error SDWebImage doesn't support Deployement Target version < 5.0
    #endif
    
    // @see https://github.com/ccgus/fmdb/commit/aef763eeb64e6fa654e7d121f1df4c16a98d9f4f
    #define SDDispatchQueueRelease(q) (dispatch_release(q))
    #if TARGET_OS_IPHONE
        #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
            #undef SDDispatchQueueRelease
            #define SDDispatchQueueRelease(q)
            #undef SDDispatchQueueSetterSementics
            #define SDDispatchQueueSetterSementics strong
        #endif
    #else
        #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
            #undef SDDispatchQueueRelease
            #define SDDispatchQueueRelease(q)
            #undef SDDispatchQueueSetterSementics
            #define SDDispatchQueueSetterSementics strong
        #endif
    #endif
    
    #if OS_OBJECT_USE_OBJC
        #define SDDispatchQueueSetterSementics strong
    #else
        #define SDDispatchQueueSetterSementics assign
    #endif
    //----------------------------end----------------------------
    

    相关文章

      网友评论

          本文标题:SDWebImage前世今生之V3.0版本

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