SDWebImage學習

SDWebImage簡介

SDWebImage是iOS開發中主流的圖像加載庫,它幫咱們處理內存緩存磁盤緩存與及圖像加載的一系列操做。使用起來方便快捷,讓咱們更好的專一於業務邏輯的開發。html

組織結構

SDWebImage框架組成以下:
SDWebImage類圖.pngweb

功能快速一覽圖:
SDWebImage架構.pngobjective-c

SDWebImageCompat 作機型適配的。
SDWebImageManager管理緩存和下載的一個類。
SDImageCache處理緩存和內存的類。
SDWebImageDownloader異步下載器專用和優化圖像加載。
SDWebImagePrefetcher圖片的預加載。緩存

源碼解析

SDImageCache

SDImageCache是繼承自NSObject的。作了cache的一些基本配置和cache的管理。
如cache的大小,cache的有效期,添加cache,刪除cache等。架構

cache的類型以下:框架

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * The image wasn't available the SDWebImage caches, but was downloaded from the web.(不緩存,從web加載)
     */
    SDImageCacheTypeNone,
    /**
     * The image was obtained from the disk cache.(磁盤緩存)
     */
    SDImageCacheTypeDisk,
    /**
     * The image was obtained from the memory cache.(內存緩存)
     */
    SDImageCacheTypeMemory
};

內部的AutoPurgeCache是繼承自NSCache的,主要用途是在收到系統的UIApplicationDidReceiveMemoryWarningNotification通知時,清理內存緩存。異步

此外,SDWebCache的內存緩存也是使用的NSCache.
設置緩存的核心方法以下:async

- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
    if (!image || !key) {
        return;
    }
    // if memory cache is enabled
    if (self.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }

    if (toDisk) {
        dispatch_async(self.ioQueue, ^{
            NSData *data = imageData;

            if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
                // We need to determine if the image is a PNG or a JPEG
                // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)
                // The first eight bytes of a PNG file always contain the following (decimal) values:
                // 137 80 78 71 13 10 26 10

                // If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download)
                // and the image has an alpha channel, we will consider it PNG to avoid losing the transparency
                int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
                BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                                  alphaInfo == kCGImageAlphaNoneSkipFirst ||
                                  alphaInfo == kCGImageAlphaNoneSkipLast);
                BOOL imageIsPng = hasAlpha;

                // But if we have an image data, we will look at the preffix
                if ([imageData length] >= [kPNGSignatureData length]) {
                    imageIsPng = ImageDataHasPNGPreffix(imageData);
                }

                if (imageIsPng) {
                    data = UIImagePNGRepresentation(image);
                }
                else {
                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
                }
#else
                data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
            }

            [self storeImageDataToDisk:data forKey:key];
        });
    }
}

從代碼能夠看出,先設置的內存緩存,再設置的磁盤緩存。ide

- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {
    
    if (key == nil) {
        return;
    }

    if (self.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    if (fromDisk) {
        dispatch_async(self.ioQueue, ^{
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
            
            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }
    
}

刪除緩存的時候也是先刪除內存緩存,在刪除磁盤緩存。oop

UIImageView+WebCache

經過UIImageView集成SDWebImage異步下載和緩存遠程圖像。
下載圖片的核心方法以下:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_cancelCurrentImageLoad];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{ //保證是在主線程中設置圖片
            self.image = placeholder;
        });
    }
    
    if (url) {

        // check if activityView is enabled or not
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            [wself removeActivityIndicator];
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        dispatch_main_async_safe(^{
            [self removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

從代碼能夠看出,代碼使用時runtime爲分類添加屬性,設置placeholder必定是在住線程中進行的,圖片真正的下載操做,使用的是SDWebImageOperation

圖片加載的時序圖:
圖片加載時序圖

並且這裏有兩個值得咱們學習的宏定義:

#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }

保證同步線程是在主線程執行的。

#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }

保證異步線程是在主線程執行的。

SDWebImageManager

SDWebImageManager是整個框架的一個核心類,它把SDImageCacheSDWebImageDownloader結合在一塊兒,同時管理圖片的下載和緩存操做。

SDWebImageOptions這個options,提供了咱們下載圖片時的不少可選操做。
示例以下:

/**
     * By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
     * 默認狀況下,當一個 URL 下載失敗,該URL被列入黑名單,將不會繼續嘗試下載

     * This flag disable this blacklisting.
     * 此標誌取消黑名單

     */
    SDWebImageRetryFailed = 1 << 0,

    /**
     * By default, image downloads are started during UI interactions, this flags disable this feature,
     * 默認狀況下,在 UI 交互時也會啓動圖像下載,此標記取消這一功能

     * leading to delayed download on UIScrollView deceleration for instance.
     * 會延遲下載,UIScrollView中止滾動以後再繼續下載
     * 下載事件監聽的運行循環模式是 NSDefaultRunLoopMode
     */
    SDWebImageLowPriority = 1 << 1,

    /**
     * This flag disables on-disk caching
     * 禁用磁盤緩存
     */
    SDWebImageCacheMemoryOnly = 1 << 2,

注:圖片來源

相關文章
相關標籤/搜索