SDWebImage學習
github託管地址:https://github.com/rs/SDWebImage
導入頭文件:UIImageView+WebCache.h
使用sd_setImageWithURL:緩存圖片:
1 . 方法sd_setImageWithURL:
//簡單的加載url中的圖片
[self.image sd_setImageWithURL:url]
2 . 方法 sd_setImageWithURL: placeholderImage:
//加載完成之前使用圖片placeImage作爲默認值
[self.image1 sd_setImageWithURL:url placeholderImage:placeImage];
3 . 方法sd_setImageWithURL:imagePath2 completed:
//在圖片加載完成之後執行block
[self.image2 sd_setImageWithURL:imagePath2 completed:
^(UIImage *image, NSError *error,
SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"Do something in here...");
}];
4 . 方法sd_setImageWithURL:placeholderImage:options:
//options表示緩存方式
[self.image sd_setImageWithURL:url
placeholderImage:placeImage
options:SDWebImageRetryFailed];
options每個選項含義:
//加載失敗後重試
SDWebImageRetryFailed = 1 << 0,
//UI交互期間延時下載
SDWebImageLowPriority = 1 << 1,
//只進行內存緩存
SDWebImageCacheMemoryOnly = 1 << 2,
//逐步下載,顯示圖像也是逐步的
SDWebImageProgressiveDownload = 1 << 3,
//刷新緩存
SDWebImageRefreshCached = 1 << 4,
//後臺下載
SDWebImageContinueInBackground = 1 << 5,
//通過設置NSMutableURLRequest.HTTPShouldHandleCookies = YES
//把cookies存儲在NSHTTPCookieStore
SDWebImageHandleCookies = 1 << 6,
//允許使用無效的SSL證書
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//優先下載
SDWebImageHighPriority = 1 << 8,
//延遲顯示佔位符
SDWebImageDelayPlaceholder = 1 << 9,
//進行任意圖形變換
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImage中我們使用較多的是它提供的UIImageView分類,支持從遠程服務器下載並緩存圖片。從iOS5.0開始,NSURLCache也可以處理磁盤緩存,那麼SDWebImage有什麼優勢?首先,NSURLCache是緩存原始數據(raw data)到磁盤或內存,因此每次使用的時候需要將原始數據轉換成具體的對象,如UIImage等,這會導致額外的數據解析以及內存佔用等,而SDWebImage則是緩存UIImage對象在內存,緩存在NSCache中,同時直接保存壓縮過的圖片到磁盤中;其次,當你第一次在UIImageView中使用image對象的時候,圖片的解碼是在主線程中運行的!而SDWebImage會強制將解碼操作放到子線程中。下圖是SDWebImage簡單的類圖關係:
圖片加載調用函數:
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionBlock)completedBlock {
//取消正在下載的操作
[self sd_cancelCurrentImageLoad];
//創建對象self和對象url的關聯
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/*...*/
if (url) {
//防止循環引用
__weak UIImageView *wself = self;
//由SDWebImageManager負責圖片的獲取
id <SDWebImageOperation> operation =
[SDWebImageManager.sharedManager downloadImageWithURL:url
options:options
progress:progressBlock
completed:
^(UIImage *image, NSError *error, SDImageCacheType cacheType,
BOOL finished, NSURL *imageURL) {
/*獲取圖片到主線層顯示*/
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
/*...*/
}
}
由以上函數可知圖片是從服務端、內存或者硬盤獲取是由SDWebImageManager管理的,這個類有幾個重要的屬性:
//負責管理cache,涉及內存緩存和硬盤保存
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//負責從網絡下載圖片
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
manager會根據URL先去imageCache中查找對應的圖片,如果沒有再使用downloader去下載,並在下載完成緩存圖片到imageCache,接着看實現:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
/*...*/
//根據URL生成對應的key,沒有特殊處理爲[url absoluteString];
NSString *key = [self cacheKeyForURL:url];
//去imageCache中尋找圖片
operation.cacheOperation =
[self.imageCache queryDiskCacheForKey:key
done:^(UIImage *image, SDImageCacheType cacheType) {
/*...*/
//如果圖片沒有找到,或者採用的SDWebImageRefreshCached選項,則從網絡下載
if ((!image || options & SDWebImageRefreshCached) &&
(![self.delegate respondsToSelector:@selector(
imageManager:shouldDownloadImageForURL:)]
|| [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
//如果圖片找到了,但是採用的SDWebImageRefreshCached選項,
//通知獲取到了圖片,並再次從網絡下載,使NSURLCache重新刷新
completedBlock(image, nil, cacheType, YES, url);
});
}
/*下載選項設置...*/
//使用imageDownloader開啓網絡下載
id <SDWebImageOperation> subOperation =
[self.imageDownloader downloadImageWithURL:url
options:downloaderOptions
progress:progressBlock
completed:^(UIImage *downloadedImage,
NSData *data, NSError *error,
BOOL finished) {
/*...*/
if (downloadedImage && finished) {
//下載完成後,先將圖片保存到imageCache中,然後主線程返回
recalculateFromImage:NO imageData:data
forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (!weakOperation.isCancelled) {
completedBlock(downloadedImage, nil,
SDImageCacheTypeNone, finished, url);
}
});
}
}
/*...*/
else if (image) {
//在cache中找到圖片了,直接返回
dispatch_main_sync_safe(^{
if (!weakOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
/*...*/
}
}];
return operation;
}
下面先看downloader從網絡下載的過程,下載是放在NSOperationQueue中進行的,默認maxConcurrentOperationCount爲6,timeout時間爲15s:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak SDWebImageDownloader *wself = self;
/*...*/
//防止NSURLCache和SDImageCache重複緩存
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:url
cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ?
NSURLRequestUseProtocolCachePolicy :
NSURLRequestReloadIgnoringLocalCacheData)
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headersFilter) {
request.allHTTPHeaderFields =
wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
//SDWebImageDownloaderOperation派生自NSOperation,負責圖片下載工作
operation = [[wself.operationClass alloc]
initWithRequest:request
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {/*...*/}
completed:^(UIImage *image, NSData *data,
NSError *error, BOOL finished) {/*...*/}
cancelled:^{/*...*/}];
/*...*/}];
return operation;
}
SDWebImageDownloaderOperation派生自NSOperation,通過NSURLConnection進行圖片的下載,爲了確保能夠處理下載的數據,需要在後臺運行runloop:
- (void)start {
@synchronized (self) {
/*...*/
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
/*開啓後臺下載*/
if ([self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
self.backgroundTaskId = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[[UIApplication sharedApplication]
endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
self.executing = YES;
self.connection = [[NSURLConnection alloc]
initWithRequest:self.request delegate:self startImmediately:NO];
self.thread = [NSThread currentThread];
}
[self.connection start];
if (self.connection) {
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
/*廣播通知*/
[[NSNotificationCenter defaultCenter]
postNotificationName:SDWebImageDownloadStartNotification object:self];
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
//在默認模式下運行當前runlooprun,直到調用CFRunLoopStop停止運行
CFRunLoopRun();
}
if (!self.isFinished) {
[self.connection cancel];
[self connection:self.connection
didFailWithError:[NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorTimedOut
userInfo:
@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
}
}
else {
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain
code:0
userInfo:
@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
下載過程中,在代理 - (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data中將接收到的數據保存到NSMutableData中,[self.imageData appendData:data],下載完成後在該線程完成圖片的解碼,並在完成的completionBlock中進行imageCache的緩存:
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
@synchronized(self) {
//停止當前的runLoop
CFRunLoopStop(CFRunLoopGetCurrent());
/*...*/
}
/*...*/
if (completionBlock) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
completionBlock(nil, nil, nil, YES);
}
else {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager]
cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
if (!image.images) {
//圖片解碼
image = [UIImage decodedImageWithImage:image];
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain"
code:0
userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
else {
completionBlock(image, self.imageData, nil, YES);
}
}
}
self.completionBlock = nil;
[self done];
}