SDWebImage前世今生之V3.0版本

2019-03-06  本文已影响0人  _相信自己_

3.0版本2012年11月30日发布

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

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

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

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

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

@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----------------------------
上一篇下一篇

猜你喜欢

热点阅读