SDWebImage前世今生之V3.0版本
2019-03-06 本文已影响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
- 移除
SDWebImageDecoderDelegate
和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----------------------------