SDWebImage简介

SDWebImage是iOS开发中主流的图像加载库,它帮我们处理内存缓存磁盘缓存与及图像加载的一系列操作。使用起来方便快捷,让我们更好的专注于业务逻辑的开发。

组织结构

SDWebImage框架组成如下:
SDWebImage类图.png

功能快速一览图:
SDWebImage架构.png

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.
设置缓存的核心方法如下:

- (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];
        });
    }
}

从代码可以看出,先设置的内存缓存,再设置的磁盘缓存。

- (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();
    }
    
}

删除缓存的时候也是先删除内存缓存,在删除磁盘缓存。

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,

注:图片来源


蓝光95
210 声望16 粉丝

一名从业多年的软件开发者,做过5年的iOS开发,做过一年的react-native开发,有iOS性能优化经验,IM开发经验,会小程序的开发,现在在昆明从事移动前端开发的工作。