iOS学习笔记-129.SDWebImage5——框架内部部分细节

来源:互联网 发布:linux 查看 磁盘 编辑:程序博客网 时间:2024/05/16 15:41

  • SDWebImage5框架内部部分细节
    • 一清空缓存
    • 二取消当前所有的操作
    • 三 最大并发数量
    • 四缓存文件的保存名称如何处理
    • 五该框架内部对内存警告的处理方式
    • 六该框架进行缓存处理的方式
    • 七如何判断图片的类型
    • 八队列中任务的处理方式
    • 九如何下载图片的
    • 十请求超时的时间

SDWebImage5——框架内部部分细节

一、清空缓存

//1.清空缓存//clear:直接删除缓存目录下面的文件,然后重新创建空的缓存文件//clean:清除过期缓存,计算当前缓存的大小,和设置的最大缓存数量比较,如果超出那么会继续删除(按照文件了创建的先后顺序)//过期时间:7天[[SDWebImageManager sharedManager].imageCache cleanDisk];[[SDWebImageManager sharedManager].imageCache clearMemory];[[SDWebImageManager sharedManager].imageCache clearDisk];

二.取消当前所有的操作

[[SDWebImageManager sharedManager] cancelAll];

三. 最大并发数量

最大并发数量 6

设置的地方是 SDWebImageDownloader

- (id)init

方法中。

具体如下。

当我们在 UIImageView+WebCache 的下面这个方法中

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

使用到

SDWebImageManager.sharedManager

的时候,会调用 SDWebImageManager 的init方法,如下

- (id)init {    if ((self = [super init])) {        _imageCache = [self createCache];                           //初始化imageCache(单例)        _imageDownloader = [SDWebImageDownloader sharedDownloader]; //初始化imageDownloader(单例)        _failedURLs = [NSMutableSet new];                           //初始化下载失败的URL(黑名单·空的集合)        _runningOperations = [NSMutableArray new];                  //初始化当前正在处理的任务(图片下载操作·空的可变数组)    }    return self;}

这里面会初始化 SDWebImageDownloader 的属性 imageDownloader ,这个时候回调用到 SDWebImageDownloader 的 init方法,如下

//异步下载器初始化方法- (id)init {    if ((self = [super init])) {        _operationClass = [SDWebImageDownloaderOperation class];    //获得类型        _shouldDecompressImages = YES;                              //是否解码,默认为YES(以空间换取时间)        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;   //下载任务的执行方式:默认为先进先出        _downloadQueue = [NSOperationQueue new];                    //创建下载队列:非主队列(在该队列中的任务在子线程中异步执行)        _downloadQueue.maxConcurrentOperationCount = 6;             //设置下载队列的最大并发数:默认为6        _URLCallbacks = [NSMutableDictionary new];                  //初始化URLCallbacks字典#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;                                    //设置下载超时为15秒    }    return self;}

我们可以看到 最大并发数量 = 6


四.缓存文件的保存名称如何处理?

拿到图片的URL路径,对该路径进行MD5加密

方法是 SDImageCache中的

- (NSString *)cachedFileNameForKey:(NSString *)key 

下面我们来看一下。

由前面的文章我们知道,我们的最终会调用到 SDWebImageManager 的 downloadImageWithURL 的方法。在这个方法的成功的回调里面,会进行磁盘存储。

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url                 options:(SDWebImageOptions)options                progress:(SDWebImageDownloaderProgressBlock)progressBlock               completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {        ............        //是否要进行磁盘缓存?        BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);        //如果下载策略为SDWebImageRefreshCached且该图片缓存中存在且未下载下来,那么什么都不做        if (options & SDWebImageRefreshCached && image && !downloadedImage) {            // Image refresh hit the NSURLCache cache, do not call the completion block        }        else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {            //否则,如果下载图片存在且(不是可动画图片数组||下载策略为SDWebImageTransformAnimatedImage&&transformDownloadedImage方法可用)            //开子线程处理            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{                //在下载后立即将图像转换,并进行磁盘和内存缓存                UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];#warning 2                if (transformedImage && finished) {                    BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];                    [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];                }                //在主线程中回调completedBlock                dispatch_main_sync_safe(^{                    if (!weakOperation.isCancelled) {                        completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);                    }                });            });        }    ............   }

上面会的代码中会进行内存和磁盘缓存,具体的调用是

[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];

我们进入到这个方法中看一下,好的,那么我们接下来就到了 SDImageCache 的如下代码中

//使用指定的键将图像保存到内存和可选的磁盘缓存- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {    //如果图片或对应的key为空,那么就直接返回    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) {        //异步函数+串行队列:开子线程异步处理block中的任务        dispatch_async(self.ioQueue, ^{            //拿到服务器返回的图片二进制数据            NSData *data = imageData;            //如果图片存在且(直接使用imageData||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                //获得该图片的alpha信息                int alphaInfo = CGImageGetAlphaInfo(image.CGImage);                BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||                                  alphaInfo == kCGImageAlphaNoneSkipFirst ||                                  alphaInfo == kCGImageAlphaNoneSkipLast);                //判断该图片是否是PNG图片                BOOL imageIsPng = hasAlpha;                // But if we have an image data, we will look at the preffix                if ([imageData length] >= [kPNGSignatureData length]) {                    imageIsPng = ImageDataHasPNGPreffix(imageData);                }                //如果判定是PNG图片,那么把图片转变为NSData压缩                if (imageIsPng) {                    data = UIImagePNGRepresentation(image);                }                else {                     //否则采用JPEG的方式                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);                }#else                data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];#endif            }            if (data) {                //确定_diskCachePath路径是否有效,如果无效则创建                if (![_fileManager fileExistsAtPath:_diskCachePath]) {                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];                }                // get cache Path for image key                // 根据key获得缓存路径                NSString *cachePathForKey = [self defaultCachePathForKey:key];                // transform to NSUrl                //把路径转换为NSURL类型                NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];                //使用文件管理者在缓存路径创建文件,并设置数据                [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];                // disable iCloud backup                //如果禁用了iCloud备份                if (self.shouldDisableiCloud) {                    //标记沙盒中不备份文件(标记该文件不备份)                    [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];                }            }        });    }}

上面的代码中,我们关注下面的代码

// 根据key获得缓存路径NSString *cachePathForKey = [self defaultCachePathForKey:key];

接下来我们看到 defaultCachePathForKey 方法

//获得指定 key 的默认缓存路径- (NSString *)defaultCachePathForKey:(NSString *)key {    return [self cachePathForKey:key inPath:self.diskCachePath];}

然后我们再到 - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path

//获得指定 key 对应的缓存路径- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {    //获得缓存文件的名称    NSString *filename = [self cachedFileNameForKey:key];    //返回拼接后的全路径    return [path stringByAppendingPathComponent:filename];}

发现它调用了 - (NSString *)cachedFileNameForKey:(NSString *)key方法,如下

//对key(通常为URL)进行MD5加密,加密后的密文作为图片的名称- (NSString *)cachedFileNameForKey:(NSString *)key {    const char *str = [key UTF8String];    if (str == NULL) {        str = "";    }    unsigned char r[CC_MD5_DIGEST_LENGTH];    CC_MD5(str, (CC_LONG)strlen(str), r);    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],                          r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];    return filename;}

现在我们看到了,它是通过 MD5 来处理URL地址的,然后用作文件名。


五、该框架内部对内存警告的处理方式?

内部通过监听通知的方式清理缓存

如下,

既然是内存警告的处理,那么我们想到就是缓存的问题,我们来到 SDImageCache
如下

//初始化- (id)init{    self = [super init];    if (self) {        //监听到UIApplicationDidReceiveMemoryWarningNotification(应用程序发生内存警告)通知后,调用removeAllObjects方法        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];    }    return self;}

发生内存警告的时候,清除内存中的所有缓存对象。

除此之外,还有下面的方法中也有

//使用指定的命名空间实例化一个新的缓存存储和目录- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {   ............#if TARGET_OS_IPHONE        // Subscribe to app events        //监听应用程序通知        //当监听到UIApplicationDidReceiveMemoryWarningNotification(系统级内存警告)调用clearMemory方法        [[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(clearMemory)                                                     name:UIApplicationDidReceiveMemoryWarningNotification                                                   object:nil];        //当监听到UIApplicationWillTerminateNotification(程序将终止)调用cleanDisk方法        [[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(cleanDisk)                                                     name:UIApplicationWillTerminateNotification                                                   object:nil];        //当监听到UIApplicationDidEnterBackgroundNotification(进入后台),调用backgroundCleanDisk方法        [[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(backgroundCleanDisk)                                                     name:UIApplicationDidEnterBackgroundNotification                                                   object:nil];#endif    }    return self;}

六、该框架进行缓存处理的方式

既然是看缓存的处理,那么我们还是直接看 SDImageCache 类,发现它是继承自 NSCache的,
那么也就是说它是用 NSCache 来处理缓存的。


七.如何判断图片的类型

在判断图片类型的时候,只匹配第一个字节。这个的处理在 NSData+ImageContentType

//// Created by Fabrice Aneche on 06/01/14.// Copyright (c) 2014 Dailymotion. All rights reserved.//#import "NSData+ImageContentType.h"@implementation NSData (ImageContentType)+ (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:            //WEBP :是一种同时提供了有损压缩与无损压缩的图片文件格式            // R as RIFF for WEBP            if ([data length] < 12) {                return nil;            }            //获取前12个字节            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];            //如果以『RIFF』开头,且以『WEBP』结束,那么就认为该图片是Webp类型的            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {                return @"image/webp";            }            //否则返回nil            return nil;    }    return nil;}@end@implementation NSData (ImageContentTypeDeprecated)+ (NSString *)contentTypeForImageData:(NSData *)data {    return [self sd_contentTypeForImageData:data];}@end

八、队列中任务的处理方式

任务的处理方式 FIFO。

这个体现在,SDWebImageDownloaderinit方法中

//异步下载器初始化方法- (id)init {    ····    _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;   //下载任务的执行方式:默认为先进先出    ····  return self;}

我们可以看一下,SDWebImageDownloaderExecutionOrder 枚举

//下载操作的执行方式typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {    /*     *  默认值,所有下载操作将按照队列的先进先出方式执行     */    SDWebImageDownloaderFIFOExecutionOrder,    /*     *  所有下载操作将按照堆栈的后进先出方式执行     */    SDWebImageDownloaderLIFOExecutionOrder};

九.如何下载图片的?

发送网络请求下载图片,使用NSURLConnection

具体体现,我们来到 SDWebImageDownloaderOperation 类中,看到它的 “start“`方法

//核心方法:在该方法中处理图片下载操作- (void)start {  ....    //创建NSURLConnection对象,并设置代理(没有马上发送请求)    self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];   ....   [self.connection start];    //发送网络请求    ....}

十.请求超时的时间

请求超时的时间,是 15s。

其实这个问题我们在 iOS学习笔记-128.SDWebImage4——框架内部调用简单分析 中就说过啦。

下面我们再来看一下,来到 SDWebImageDownloader 的下面这个方法中,我们会发现,超时时间就是 15s.

//核心方法:下载图片的操作- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {    ..........    //处理下载超时,如果没有设置过则初始化为15秒    NSTimeInterval timeoutInterval = wself.downloadTimeout;    if (timeoutInterval == 0.0) {        timeoutInterval = 15.0;    }    ..........}
阅读全文
0 0