你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS編程技術 >> iOS開發之SDWebImage詳解

iOS開發之SDWebImage詳解

編輯:IOS編程技術

介紹

github地址:

https://github.com/rs/SDWebImage

簡介

一個異步圖片下載及緩存的庫

特性:

  • 一個擴展UIImageView分類的庫,支持加載網絡圖片並緩存圖片
  • 異步圖片下載器
  • 異步圖片緩存和自動圖片有效期管理
  • 支持GIF動態圖片
  • 支持WebP
  • 背景圖片減壓
  • 保證同一個URL不會再次下載
  • 保證無效的URL不會重新加載
  • 保證主線程不會死鎖
  • 性能優越
  • 使用GCD和ARC
  • 支持ARM64位處理器

其他

  • 3.0後不再支持5.1.1
  • 支持Cocoapods
  • 用法不說了 github上有,挺簡單的

原理

只要有圖片的url,就能下載到圖片,使用SDWebImage的好處就是緩存機制,每次取圖片先判斷是否在內存中,再到緩存中查找,找到了直接加載,在緩存中找不到才重新下載,url也會記錄,是否是失效的url,是則不會再嘗試。下載到的圖片會緩存,用於下次可以直接加載。圖片下載,解碼,轉換都異步進行,不會阻塞主線程。

類的作用

SDImageCache

設置緩存的類型,方式,路徑等

SDWebImageCompat

兼容類,定義了很多宏和一個轉換圖片的方法

SDWebImageDecoder

解碼器,讓圖片色彩轉換(涉及到color space)

SDWebImageDownloader

下載器,設置下載相關,要用到SDWebImageDownloaderOperation

SDWebImageDownloaderOperation

下載器的操作

SDWebImageManager

管理圖片下載,取消操作,判斷url是否已緩存等

SDWebImageOperation

圖片操作,後面很多類都要用到

SDWebImagePrefetcher

預抓取器,預先下載urls中的圖片

UIButton+WebCache

按鈕圖片的緩存

UIImage+GIF

緩存gif

NSData+ImageContentType

判斷圖片的類型,png/jpeg/gif/webp

UIImage+MultiFormat

緩存多種格式的圖片,要用到NSData+ImageContentType的判斷圖片類型方法和UIImage+GIF的判斷是否為gif圖片方法,以及ImageIO裡面的方法

UIImageView+HighlightedWebCache

緩存高亮圖片

UIImageView+WebCache

主要用到這個,加載及緩存UIImageView的圖片

UIView+WebCacheOperation

緩存的操作,有緩存,取消操作,移除緩存

源碼解析

先講一些比較邊緣的方法

0.SDWebImageOperation

圖片操作,只有頭文件,定義了協議SDWebImageOperation,裡面也只有取消方法
這個類後面很多類都要用到。

@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end

  

1.NSData+ImageContentType

這個文件是NSData的分類,只有一個方法,傳入圖片數據,根據圖片的頭標識來確定圖片的類型。頭標識都不一樣,只需獲取文件頭字節,對比十六進制信息,判斷即可。
圖片文件頭標識十六進制頭字節 jpeg/jpg FFD8 0xFF png 8950 0x89 gif 4749 0x47 tiff 4D4D / 4949 0x49/0x4D

Webp格式開頭是0x52,但是還有可能是其他類型文件,所以要識別前綴為
52 49 46 46 對應 RIFF
後綴 57 45 42 50 對應 WEBP,符合這些條件的才是webp圖片文件

+ (NSString *)sd_contentTypeForImageData:(NSData *)data {
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return @"image/jpeg";
        case 0x89:
            return @"image/png";
        case 0x47:
            return @"image/gif";
        case 0x49:
        case 0x4D:
            return @"image/tiff";
        case 0x52:
            // R as RIFF for WEBP
            if ([data length] < 12) {
                return nil;
            }

            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return @"image/webp";
            }

            return nil;
    }
    return nil;
}

  


2.SDWebImageCompat

兼容類,這個類定義了很多宏還有一個伸縮圖片的方法,宏就不說了

這個方法定義成C語言式的內聯方法
核心代碼如下,傳入key和圖片,如果key中出現@2x就設定scale為2.0,出現@3x就設定scale為3.0,然後伸縮圖片

CGFloat scale = [UIScreen mainScreen].scale;
if (key.length >= 8) {
    NSRange range = [key rangeOfString:@"@2x."];
    if (range.location != NSNotFound) {
        scale = 2.0;
    }

    range = [key rangeOfString:@"@3x."];
    if (range.location != NSNotFound) {
        scale = 3.0;
    }
}

UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
image = scaledImage;

  


3.SDWebImageDecoder
這個是解碼器類,只定義了一個解碼方法,傳入圖片,返回的也是圖片

CGImageRef是一個指針類型。typedef struct CGImage *CGImageRef;
獲取傳入圖片的alpha信息,然後判斷是否符合蘋果定義的CGImageAlphaInfo,如果是就返回原圖片

        CGImageRef imageRef = image.CGImage;

        CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
        BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                         alpha == kCGImageAlphaLast ||
                         alpha == kCGImageAlphaPremultipliedFirst ||
                         alpha == kCGImageAlphaPremultipliedLast);

        if (anyAlpha) { return image; }

  

然後獲取圖片的寬高和color space(指定顏色值如何解釋),判斷color space是否支持,不支持就轉換為支持的模式(RGB),再用圖形上下文根據獲得的信息畫出來,釋放掉創建的CG指針再返回圖片

        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);

        // current
        CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
        CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);

        bool unsupportedColorSpace = (imageColorSpaceModel == 0 || imageColorSpaceModel == -1 || imageColorSpaceModel == kCGColorSpaceModelCMYK || imageColorSpaceModel == kCGColorSpaceModelIndexed);
        if (unsupportedColorSpace)
            colorspaceRef = CGColorSpaceCreateDeviceRGB();

        CGContextRef context = CGBitmapContextCreate(NULL, width,
                                                     height,
                                                     CGImageGetBitsPerComponent(imageRef),
                                                     0,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(context);
        UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha scale:image.scale orientation:image.imageOrientation];
        if (unsupportedColorSpace)
            CGColorSpaceRelease(colorspaceRef);
        CGContextRelease(context);
        CGImageRelease(imageRefWithAlpha);

        return imageWithAlpha;

  


這個算是核心部分

4.UIView+WebCacheOperation

緩存操作的UIView的分類,支持三種操作,也是整個庫中比較核心的操作。

但是首先我們來了解三種操作都要用到的存儲數據的方法。
這兩個方法用的是OC中runtime方法,原理是兩個文件關聯方法,和上層的存儲方法差不多,傳入value和key對應,取出也是根據key取出value
object傳入self即可

1.設置關聯方法
//傳入object和key和value,policy
//policy即存儲方式,和聲明使用幾種屬性大致相同,有copy,retain,copy,retain_nonatomic,assign 五種)

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

  

2.取出方法
//傳入object和key返回value
id objc_getAssociatedObject(id object, const void *key)

這個方法是三種操作都要用到的,獲得數據
這個方法是使用前面兩個方法,根據緩存加載數據
有緩存則從緩存中取出數據,沒有則緩存數據,返回格式是字典格式

- (NSMutableDictionary *)operationDictionary {
    NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    if (operations) {
        return operations;
    }
    operations = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}

  

接下來是三種操作

一.加載圖片根據是否有緩存

從獲得數據方法獲得數據,傳入key,先調用第二個方法停止操作,再根據key緩存數據

- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {
    [self sd_cancelImageLoadOperationWithKey:key];
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    [operationDictionary setObject:operation forKey:key];
}

  

二.取消加載圖片如果有緩存

先獲得方法一的返回字典數據,傳入key在返回的字典中查找是否已經存在,如果存在則取消所有操作
conformsToProtocol方法如果符合這個協議(協議中聲明了取消方法),調用協議中的取消方法

- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    // Cancel in progress downloader from queue
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    id operations = [operationDictionary objectForKey:key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

  

三.移除緩存

獲得方法一的數據,傳入key如果key對應的數據在數據中則移除

- (void)sd_removeImageLoadOperationWithKey:(NSString *)key {
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    [operationDictionary removeObjectForKey:key];
}

  


5.SDWebImageDownloader

下載器類,需要用到SDWebImageDownloaderOperation類,下載器操作,後面會說到

定義了一些屬性

//下載隊列的最大下載數
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
//當前下載數
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
//下載超時的時間
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
//是否解壓圖片,默認是
@property (assign, nonatomic) BOOL shouldDecompressImages;
//下載器順序,枚舉類型,有兩種,先進先出,還是後進先出
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;

#####還有一些用戶屬性
//url證書

@property (strong, nonatomic) NSURLCredential *urlCredential;
//用戶名
@property (strong, nonatomic) NSString *username;
//密碼
@property (strong, nonatomic) NSString *password;
//頭像過濾器,block指針類型,接受url和字典headers
@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;

  

init方法

初始化了一些屬性和寫好http請求頭

- (id)init {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES;
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new];
        _downloadQueue.maxConcurrentOperationCount = 6;
        _URLCallbacks = [NSMutableDictionary new];
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 15.0;
    }
    return self;
}

  

核心方法

傳入url,下載器選項(接下來會說),進度block,完成回調block

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageDownloaderOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageDownloaderCompletedBlock)completedBlock;

  

這個方法非常復雜,定義了http請求,定義了SDWebImageDownloaderOperation實例,即下載器操作,初始化過程非常復雜,用到了http請求,用到了前面定義的那些屬性,最後返回這個操作,這個過程建議去看源碼


6.SDWebImageDownloaderOperation

下載器的操作
直接看前面下載器需要用到的初始化方法
需要初始化了各種屬性,主要是幾個block,進度block,完成回調block,取消回調block

- (id)initWithRequest:(NSURLRequest *)request
              options:(SDWebImageDownloaderOptions)options
             progress:(SDWebImageDownloaderProgressBlock)progressBlock
            completed:(SDWebImageDownloaderCompletedBlock)completedBlock
            cancelled:(SDWebImageNoParamsBlock)cancelBlock {
    if ((self = [super init])) {
        _request = request;
        _shouldDecompressImages = YES;
        _shouldUseCredentialStorage = YES;
        _options = options;
        _progressBlock = [progressBlock copy];
        _completedBlock = [completedBlock copy];
        _cancelBlock = [cancelBlock copy];
        _executing = NO;
        _finished = NO;
        _expectedSize = 0;
        responseFromCached = YES; 
    }
    return self;
}

  


7.SDWebImageManager

圖片管理器,負責圖片的下載,轉換,緩存等
這裡先說明SDWebImageOptions
1 << X 這種是位運算符,1左移多少位,後面要用到,說明一下

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,//無效url會加入黑名單,這個標志是禁用黑名單
SDWebImageLowPriority = 1 << 1, //低優先級,會後下載
SDWebImageCacheMemoryOnly = 1 << 2, //禁用磁盤緩存
SDWebImageProgressiveDownload = 1 << 3, //顯示下載進度,下載完才顯示
SDWebImageRefreshCached = 1 << 4, //重新從遠程緩存
SDWebImageContinueInBackground = 1 << 5, //在後台繼續下載圖片
SDWebImageHandleCookies = 1 << 6, //把cookie存儲到NSHTTPCookieStorey
SDWebImageAllowInvalidSSLCertificates = 1 << 7, //允許非信任ssl證書
SDWebImageHighPriority = 1 << 8, //高優先級,插隊下載隊列
SDWebImageDelayPlaceholder = 1 << 9, //顯示的是替代圖片(初始化圖片)
SDWebImageTransformAnimatedImage = 1 << 10, //轉換圖片大小
SDWebImageAvoidAutoSetImage = 1 << 11 //避免自動設置圖片(想手動的時候設置)
};

  

這裡包含了各種選擇

核心方法
傳入url,上面的options,進度block,完成回調block

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
?completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

  

實例化過程請去看源碼

說下其他方法
一個傳入key判斷圖片是否存在存儲空間的方法
使用的是NSFileManager的方法

- (BOOL)diskImageExistsWithKey:(NSString *)key {
    BOOL exists = NO;
    exists = [[NSFileManager defaultManager] fileExistsAtPath:[self defaultCachePathForKey:key]];

    if (!exists) {
        exists = [[NSFileManager defaultManager] fileExistsAtPath:[[self defaultCachePathForKey:key] stringByDeletingPathExtension]];
    }
    return exists;
}

  

還有從存儲空間或者緩存取出圖片的方法
self.memCache是AutoPurgeCache(單純繼承自NSCache)的實例
從存儲空間取圖片要先判斷內存中是否存在,然後才從存儲空間中查找

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
    return [self.memCache objectForKey:key];
}

- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {

    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        return image;
    }
    UIImage *diskImage = [self diskImageForKey:key];
    if (diskImage && self.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }
    return diskImage;
}

- (UIImage *)diskImageForKey:(NSString *)key {
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        UIImage *image = [UIImage sd_imageWithData:data];
        image = [self scaledImageForKey:key image:image];
        if (self.shouldDecompressImages) {
            image = [UIImage decodedImageWithImage:image];
        }
        return image;
    }
    else {
        return nil;
    }
}

  

還有幾個清除方法

- (void)clearDisk;
- (void)clearMemory;

- (void)cleanDisk;
- (void)cleanDiskWithCompletionBlock;
...

  


8.SDWebImagePrefetcher

預抓取器,用來預抓取圖片
核心方法

//預抓取圖片
- (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock;
//取消預抓取圖片
- (void)cancelPrefetching;

  

先來看預抓取圖片
傳入url,進度block,完成回調block
首先取消抓取,然後重新開始

- (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock {
    [self cancelPrefetching]; // Prevent duplicate prefetch request
    self.startedTime = CFAbsoluteTimeGetCurrent();
    self.prefetchURLs = urls;
    self.completionBlock = completionBlock;
    self.progressBlock = progressBlock;

    if (urls.count == 0) {
        if (completionBlock) {
            completionBlock(0,0);
        }
    } else {
        NSUInteger listCount = self.prefetchURLs.count;
        for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) {
            [self startPrefetchingAtIndex:i];
        }
    }
}

  

最後調用startPrefetchingAtIndex:方法,再調用self.manager的核心方法,即開始下載圖片

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

  


最後是各種分類,即直接再初始化的控件設置圖片,支持UIButton,UIImage,UIImageView,大同小異,我直接說UIImageView+WebCache

9.UIImageView+WebCache

很多加載方法最終都會以缺省參數方式或者直接調用這個方法,傳入一個URL,一個用來初始化的image,一個options(枚舉,下面詳細說明),一個progressBlock(返回圖片接受進度等),一個completedBlock(完成回調block)

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

  

首先根據url緩存圖片,這裡用到的是OC的runtime中的關聯方法(見4)

    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

  

然後判斷options(見7)是其他選擇則直接給圖片賦值placehoder圖片,這裡判斷使用的是 & 與 位運算符,SDWebImageDelayPlacehoder是 1 << 9,1左移9位與options相與

    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }

  

如果url存在,則定義圖片操作,使用圖片管理器的單例來調用核心方法(下載圖片方法)

        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
    //過程省略

}

  


10.UIImage+GIF

gif的實現使用了ImageIO中的CGImageSourceRef
用獲得的gif數據得到CGImageSourceRef,然後算出時間,在這個時間內把圖片一幀一幀的放進一個數組,最後再把這個數組和時間轉成圖片,就成了gif

+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
    if (!data) {
        return nil;
    }

    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);

    size_t count = CGImageSourceGetCount(source);

    UIImage *animatedImage;

    if (count <= 1) {
        animatedImage = [[UIImage alloc] initWithData:data];
    }
    else {
        NSMutableArray *images = [NSMutableArray array];

        NSTimeInterval duration = 0.0f;

        for (size_t i = 0; i < count; i++) {
            CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);

            duration += [self sd_frameDurationAtIndex:i source:source];

            [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];

            CGImageRelease(image);
        }

        if (!duration) {
            duration = (1.0f / 10.0f) * count;
        }

        animatedImage = [UIImage animatedImageWithImages:images duration:duration];
    }

    CFRelease(source);

    return animatedImage;
}

  

總結

看完SDWebImage的源碼後感覺學到了很多東西,特別是緩存那一塊寫的特別好。ImageIO和objc/runtime很值得學習一下。也感覺到設計出這樣一個庫需要很強大的知識面,非常嚴謹的思想,真的不容易。

 

 

何問起

 公眾號,經常寫一些開源框架源碼解析總結等,高質量文章
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved