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

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

    ..........

}

你可能感兴趣的:(iOS学习-iOS,ios,框架,图片,缓存)