美文网首页
YYWebImage源码解析

YYWebImage源码解析

作者: philiha | 来源:发表于2017-09-22 19:57 被阅读0次

    表示一下对原作者的崇敬之情

    YYWebImage是大神郭的框架,专门用来做图片下载的...相对于每次都要SDWebImage+FLAnimatedImage来说,只导入一个YYWebImage.h还是相当方便的(额..就算是比较方便吧..谁没事老导框架玩..)

    基本用法

    1.从 URL 加载图片

    // 加载网络图片
    imageView.yy_imageURL = [NSURL URLWithString:@"http://github.com/logo.png"];
        
    // 加载本地图片
    imageView.yy_imageURL = [NSURL fileURLWithPath:@"/tmp/logo.png"];
    

    2.加载动图

    // 只需要把 `UIImageView` 替换为 `YYAnimatedImageView` 即可。
    UIImageView *imageView = [YYAnimatedImageView new];
    imageView.yy_imageURL = [NSURL URLWithString:@"http://github.com/ani.webp"];
    

    3.渐进式图片加载

    // 渐进式:边下载边显示
    [imageView yy_setImageWithURL:url options:YYWebImageOptionProgressive];
        
    // 渐进式加载,增加模糊效果和渐变动画 (见本页最上方的GIF演示)
    [imageView yy_setImageWithURL:url options:YYWebImageOptionProgressiveBlur | YYWebImageOptionSetImageWithFadeAnimation];
    

    4.加载、处理图片

    // 1. 下载图片
    // 2. 获得图片下载进度
    // 3. 调整图片大小、加圆角
    // 4. 显示图片时增加一个淡入动画,以获得更好的用户体验
        
    [imageView yy_setImageWithURL:url
       placeholder:nil
       options:YYWebImageOptionSetImageWithFadeAnimation
       progress:^(NSInteger receivedSize, NSInteger expectedSize) {
           progress = (float)receivedSize / expectedSize;
       }
       transform:^UIImage *(UIImage *image, NSURL *url) {
           image = [image yy_imageByResizeToSize:CGSizeMake(100, 100) contentMode:UIViewContentModeCenter];
           return [image yy_imageByRoundCornerRadius:10];
       }
       completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
           if (from == YYWebImageFromDiskCache) {
               NSLog(@"load from disk cache");
           }
       }];
    

    5.图片缓存

    YYImageCache *cache = [YYWebImageManager sharedManager].cache;
        
    // 获取缓存大小
    cache.memoryCache.totalCost;
    cache.memoryCache.totalCount;
    cache.diskCache.totalCost;
    cache.diskCache.totalCount;
        
    // 清空缓存
    [cache.memoryCache removeAllObjects];
    [cache.diskCache removeAllObjects];
        
    // 清空磁盘缓存,带进度回调
    [cache.diskCache removeAllObjectsWithProgressBlock:^(int removedCount, int totalCount) {
       // progress
    } endBlock:^(BOOL error) {
       // end
    }];
    

    以上抄自大神郭的GitHub

    UIImageView+YYWebImage类

    当我们点进方法以后,发现这是一个Category 方法,里面提供了很多方法,单都是基于一个方法扩展的:

    - (void)setYy_imageURL:(NSURL *)imageURL {
        [self yy_setImageWithURL:imageURL
                     placeholder:nil
                         options:kNilOptions
                         manager:nil
                        progress:nil
                       transform:nil
                      completion:nil];
    }
    

    里面可以传图片的url,默认图片,动画效果类型,下载类型,下载管理器,提供的block:进度条回调,完成回调。

    这个方法是整个Category的核心,代码比较长,我们分开看。
    首先是初始化一个YYWebImageManager,然后动态的添加_YYWebImageSetter属性,为的是管控整个YYImage的下载,查找有没有相同的url在下载,如果有的话就要取消操作,确保同一个url只有一个队列在下载处理:

    _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
        if (!setter) {
            setter = [_YYWebImageSetter new];
            objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        int32_t sentinel = [setter cancelWithNewURL:imageURL];
    

    紧接着YYImage会切到主线程中,做一些配置,设置一下动画,设置一下默认图片,根据刚才的options从不同的地方获取图片:

    if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
                !(options & YYWebImageOptionAvoidSetImage)) {
                if (!self.highlighted) {
                    [self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
                }
            }
    
            if (!imageURL) {
                if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
                    self.image = placeholder;
                }
                return;
            }
    
            // get the image from memory as quickly as possible
            UIImage *imageFromMemory = nil;
            if (manager.cache &&
                !(options & YYWebImageOptionUseNSURLCache) &&
                !(options & YYWebImageOptionRefreshImageCache)) {
                imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
            }
            if (imageFromMemory) {
                if (!(options & YYWebImageOptionAvoidSetImage)) {
                    self.image = imageFromMemory;
                }
                if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
                return;
            }
    
            if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
                self.image = placeholder;
            }
    
    

    这里要注意的是他会在YYImageCacheMemory中寻找一次图片,也就是他默认先在内存中找一下。但是默认的方法中,我们的图片是存在YYImageCacheDisk中的。
    所以现在YYImage并没有找到图片,继续往执行,他开出一个异步线程进行下载:

    dispatch_async([_YYWebImageSetter setterQueue], ^{
                YYWebImageProgressBlock _progress = nil;
                if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        progress(receivedSize, expectedSize);
                    });
                };
    
                __block int32_t newSentinel = 0;
                __block __weak typeof(setter) weakSetter = nil;
                YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
                    __strong typeof(_self) self = _self;
                    BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
                    dispatch_async(dispatch_get_main_queue(), ^{
                        BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
                        if (setImage && self && !sentinelChanged) {
                            BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted);
                            if (showFade) {
                                CATransition *transition = [CATransition animation];
                                transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
                                transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
                                transition.type = kCATransitionFade;
                                [self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
                            }
                            self.image = image;
                        }
                        if (completion) {
                            if (sentinelChanged) {
                                completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
                            } else {
                                completion(image, url, from, stage, error);
                            }
                        }
                    });
                };
    
                newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
                weakSetter = setter;
            });
    
    

    核心代码是:
    newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];

    YYWebImageOperation.h类

    点进去以后看,发现又是一坨,仔细看会发现,大部分是赋值和一些判断,最关键的创建一个请求去获取图片:

    NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion];
    

    作者对NSOperation进行了自定义,他创建了一个YYWebImageOperation,这是YYImage请求的核心。首先他重写了start方法

    - (void)start {
        @autoreleasepool {
            [_lock lock];
            self.started = YES;
            if ([self isCancelled]) {
                [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
                self.finished = YES;
            } else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
                if (!_request) {
                    self.finished = YES;
                    if (_completion) {
                        NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
                        _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
                    }
                } else {
                    self.executing = YES;
                    [self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
                    if ((_options & YYWebImageOptionAllowBackgroundTask) && _YYSharedApplication()) {
                        __weak __typeof__ (self) _self = self;
                        if (_taskID == UIBackgroundTaskInvalid) {
                            _taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{
                                __strong __typeof (_self) self = _self;
                                if (self) {
                                    [self cancel];
                                    self.finished = YES;
                                }
                            }];
                        }
                    }
                }
            }
            [_lock unlock];
        }
    }
    

    这个start方法主要是对队列的一些控制,他会再分出一个网络线程networkThread去做网络请求工作。我们顺着往下看,他进入了:
    -(void)_startOperation
    这里还没进入请求,YYImage会去查看硬盘内存中的图片,如果有就会执行:
    UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
    如果没有的话,就会去执行_startRequest方法,然后我们就看到了最重要的一段代码:

    if (![self isCancelled]) {
                _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[_YYWebImageWeakProxy proxyWithTarget:self]];
                if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                    [YYWebImageManager incrementNetworkActivityCount];
                }
            }
    

    这里请求还是用的快要淘汰的NSURLConnection,- -让人潸然泪下....接下来就是一些回调了。整个流程结束!
    整个流程图如下:

    image.png

    小总结

    由此看出YYImage使用非常简单,但内部构造比较复杂。主要的核心模块是:_YYWebImageSetter,YYImageCache,YYWebImageManager,YYWebImageOperation。分工有序,结构清晰,美中不足的是请求方式还是基于快要淘汰的NSURLConnection。不过要修改起来也比较容易,之后我会对YYImage的其他功能做介绍分析,如果你感兴趣可以关注我。

    加载Gif动态图

    基本使用

    YYImage加载gif使用的是YYAnimatedImageView类。我们首先要新建一个YYAnimatedImageView对象:
    YYAnimatedImageView *imageView=[YYAnimatedImageView new];
    然后后两种加载UIimage的方式:
    1.通过URL加载
    NSURL *path = [[NSBundle mainBundle]URLForResource:@"guidegif" withExtension:@"gif"]; imageView.yy_imageURL = path;
    2.通过YYImage加载
    NSURL *path = [[NSBundle mainBundle]URLForResource:@"guidegif_loop" withExtension:@"gif"]; YYImage * image = [YYImage imageWithContentsOfFile:path.path]; imageView.image = image;

    YYAnimatedImageView类

    他是YYImage加载gif的专供类。他继承于UIImageView,提供了位数不多的几个接口:

    @property (nonatomic) BOOL autoPlayAnimatedImage;
    @property (nonatomic) NSUInteger currentAnimatedImageIndex;
    @property (nonatomic, readonly) BOOL currentIsPlayingAnimation;
    @property (nonatomic, copy) NSString *runloopMode;
    @property (nonatomic) NSUInteger maxBufferSize;
    

    其中一个还是只读的,并不能设置。这里很不人性化,因为连最起码的loop数量都不开放出来,都写在了.m里面。

    我们进入YYAnimatedImageView.m后会发现其实YYAnimatedImageView作为子类重写了很多父类的方法,所以很多设置方法我们要点入进去才能看到。我们顺着运行顺序看下去,首先是对image属性的赋值,这里面最核心的方法是:

    - (void)setImage:(id)image withType:(YYAnimatedImageType)type {
        [self stopAnimating];
        if (_link) [self resetAnimated];
        _curFrame = nil;
        switch (type) {
            case YYAnimatedImageTypeNone: break;
            case YYAnimatedImageTypeImage: super.image = image; break;
            case YYAnimatedImageTypeHighlightedImage: super.highlightedImage = image; break;
            case YYAnimatedImageTypeImages: super.animationImages = image; break;
            case YYAnimatedImageTypeHighlightedImages: super.highlightedAnimationImages = image; break;
        }
        [self imageChanged];
    }
    

    所有的对image的设置都会走到这里。主要是暂停动画,然后对image的一个设置,同时进入imageChanged。imageChanged里面主要是一些逻辑处理没什么说的,里面最关键的一句就是:
    [self resetAnimated];
    resetAnimated是整个实现gif动画的核心,想要高效的展现gif动画就必须重写系统的动画。那这里YYImage的实现方式和FLImage是一样,通过CADisplayLink定时器去绘制gif动画。这样就会使得内存大大的减少,但是CPU的占用会比较大,是以时间换空间的做法:

    - (void)resetAnimated {
        dispatch_once(&_onceToken, ^{
            _lock = dispatch_semaphore_create(1);
            _buffer = [NSMutableDictionary new];
            _requestQueue = [[NSOperationQueue alloc] init];
            _requestQueue.maxConcurrentOperationCount = 1;
            _link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)];
            if (_runloopMode) {
                [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
            }
            _link.paused = YES;
    
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
        });
    
        [_requestQueue cancelAllOperations];
        LOCK(
             if (_buffer.count) {
                 NSMutableDictionary *holder = _buffer;
                 _buffer = [NSMutableDictionary new];
                 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
                     // Capture the dictionary to global queue,
                     // release these images in background to avoid blocking UI thread.
                     [holder class];
                 });
             }
        );
        _link.paused = YES;
        _time = 0;
        if (_curIndex != 0) {
            [self willChangeValueForKey:@"currentAnimatedImageIndex"];
            _curIndex = 0;
            [self didChangeValueForKey:@"currentAnimatedImageIndex"];
        }
        _curAnimatedImage = nil;
        _curFrame = nil;
        _curLoop = 0;
        _totalLoop = 0;
        _totalFrameCount = 1;
        _loopEnd = NO;
        _bufferMiss = NO;
        _incrBufferCount = 0;
    }
    

    这里要注意的是,yyimage对播放做了优化,他在显示了一张图片后,立马缓存好下一张为接下来的播放做准备,这就是他比FL更流程的关键,这句代码在他的:
    -(void)step:(CADisplayLink *)link
    方法中,这个方法是被CADisplayLink绑定了的。
    优化的代码:

    if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity
            _YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
            operation.view = self;
            operation.nextIndex = nextIndex;
            operation.curImage = image;
            [_requestQueue addOperation:operation];
        }
    

    整个流程大致如下:

    image.png

    小总结

    YYImage播放gif的能力,是我见过的图片库中最强的。他的流畅和易用值得我们把FL替换掉。美中不足的是他给我们提供的接口太少,我们能完成的功能也就是不停的播放gif。虽然YYImage还提供了一个YYSpriteSheetImage,但是配置比较复杂,而且不能加载gif,只能是图片数组。加载UIImage的时候,推荐用第二种方法--先变成YYImage,因为直接用url可能一开始会找不到图片,造成屏幕闪烁的情况。

    YYImageCache

    这个类内容比较简单,api也都 通俗易懂,仅仅把注释贴上来即可,深究为什么这么简单,其实还是因为这个类做的事情其实仅仅是统一的调用YYMemoryCache,YYMemoryCache来存取图片而已,具体的存储细节都实现在YYCache里面了,所以这里使用起来才会简单轻松
    先看开放了哪些api,都是什么意思

    ///图片缓存类型
    typedef NS_OPTIONS(NSUInteger, YYImageCacheType) {
        /// No value.
        YYImageCacheTypeNone   = 0,
    
        /// Get/store image with memory cache.//从内存中获取
        YYImageCacheTypeMemory = 1 << 0,
    
        /// Get/store image with disk cache.//从磁盘中获取
        YYImageCacheTypeDisk   = 1 << 1,
    
        /// Get/store image with both memory cache and disk cache.//同时获取
        YYImageCacheTypeAll    = YYImageCacheTypeMemory | YYImageCacheTypeDisk,
    };
    
    /**
     *  YYImageCache是一个用来存储UIImage和image数据的缓存,是基于内存缓存与磁盘缓存实现的
    
     @discussion 磁盘缓存会尝试保护原始的图片数据
     如果原始的图片仍是image,会保存为一个png或者jpeg
     如果原始图片是一个gif,apng,webp动图,会保存为原始格式
     如果原始图片缩放比例不是1,那么缩放值会被保存为一个缩放的数据
     虽然图片能被NSCoding协议解码,但是这不是一个最优解:
     苹果的确使用UIImagePNGRepresentation()来解码所有类型的图片,但是可能会丢失原始的可变帧数据.结果就是打包成plist文件不能直接查看照片.如果图片没有alpha通道,使用JPEG代理PNG能够保存更多的尺寸和编解码时间.
     */
    @interface YYImageCache : NSObject
    
    //缓存名字,默认为nil
    @property (copy) NSString *name;
    
    //内存缓存,具体信息看YYMemoryCache
    @property (strong, readonly) YYMemoryCache *memoryCache;
    
    //磁盘缓存,具体信息看YYDiskCache
    @property (strong, readonly) YYDiskCache *diskCache;
    
    /**
     *  当从磁盘缓存请求图片的时候是否解码动图,默认为YES
     @discussion 当从磁盘缓存读取图片,会使用YYImage来解码比如WebP/APNG/GIF格式的动图,设置这个值为NO可以忽略动图
     */
    @property (assign) BOOL allowAnimatedImage;
    
    /**
     *  是否解码图片存储位图,默认为YES
     @discussion 如果这个值为YES,图片会通过位图解码来获得更好的用户体验,但是可能会消耗更大的内存资源
     */
    @property (assign) BOOL decodeForDisplay;
    
    
    - (instancetype)init UNAVAILABLE_ATTRIBUTE;
    + (instancetype)new UNAVAILABLE_ATTRIBUTE;
    
    /**
     *  单例类初始化方法
     */
    + (instancetype)sharedCache;
    
    /**
     *  初始化方法,在多个情况下访问同一个路径会导致缓存不稳定
     *
     *  @param path cache读写的全路径,只初始化一次,你不应该来读写这个路径
     *
     *  @return 一个新的缓存对象,或者返回带nil带error信息
     */
    - (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
    
    
    /**
     *  把图片通过一个具体的key存进缓存,同时memory跟disk都会存,这个方法会立刻返回,在后台线程执行
     *
     *  @param image 如果为nil这个方法无效
     *  @param key 存储图片的key,为nil这个方法无效
     */
    - (void)setImage:(UIImage *)image forKey:(NSString *)key;
    
    /**
     *  通过一个key把图片缓存,这个方法会立刻返回并在后台执行
        如果'type'包括'YYImageCacheTypeMemory',那么图片会被存进memory,如果image为nil会用'imageData'代理
        如果'type'包括'YYImageCacheTypeDisk',那么'imageData'会被存进磁盘缓存,如果'imageData'为nil会用image代替
     //这里可以看到作者一个思想,如果存进memory,直接存image,会减小很多解码的消耗,如果存disk,会存imageData
     *
     */
    - (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type;
    
    /**
     *  通过key移除cache中的一个图片,memory跟disk会同时移除
        这个方法会立刻返回并在后台线程执行
     *
     *  @param key 移除图片用的key,为nil的话这个方法没啥用
     */
    - (void)removeImageForKey:(NSString *)key;
    
    /**
     *  从缓存中通过key删图片
     这个方法会立刻返回并在后台线程执行
     *
     *  @param key  key
     *  @param type 从哪删除,跟上个方法不同,这个可以删除指定类型的缓存
     */
    - (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type;
    
    /**
     *  通过key检查缓存中是否有某个图片
        如果图片不在内存中,这个方法可能会阻塞线程,知道这个文件读取完毕
     *
     *  @param key key,为nil时返回NO
     *
     */
    - (BOOL)containsImageForKey:(NSString *)key;
    
    /**
     *  跟上个差不多,只不过可以查具体类型的缓存
     */
    - (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type;
    
    /**
     *  通过key获取图片,如果图片不在内存中,这个方法可能会阻塞线程知道文件读取完毕
     *
     *  @param key 一个字符串类型图片缓存key,为nil方法返回nil
     *
     *  @return 通过key查到的图片,没有图片就是nil
     */
    - (UIImage *)getImageForKey:(NSString *)key;
    
    /**
     *  跟上个方法差不多,只不过从指定缓存类型中获取图片
     */
    - (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type;
    
    /**
     *  通过key异步的获取图片
     *
     *  @param key   key
     *  @param type  缓存类型
     *  @param block 完成的block回调,主线程调用的
     */
    - (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void(^)(UIImage *image, YYImageCacheType type))block;
    
    /**
     *  通过key查找图片数据data格式,方法会阻塞主线程知道文件读取完毕
     *
     *  @param key key
     *
     *  @return 图片数据,查不到为nil
     */
    - (NSData *)getImageDataForKey:(NSString *)key;
    
    /**
     *  通过key来异步的获取图片数据
     *
     *  @param key   <#key description#>
     *  @param block 主线程的完成回调
     */
    - (void)getImageDataForKey:(NSString *)key withBlock:(void(^)(NSData *imageData))block;
    

    其实现细节如下:

    static inline dispatch_queue_t YYImageCacheIOQueue() {
    #ifdef YYDispatchQueuePool_h
        return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);
    #else
        return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    #endif
    }
    
    static inline dispatch_queue_t YYImageCacheDecodeQueue() {
    #ifdef YYDispatchQueuePool_h
        return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
    #else
        return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    #endif
    }
    
    
    @interface YYImageCache ()
    - (NSUInteger)imageCost:(UIImage *)image;
    - (UIImage *)imageFromData:(NSData *)data;
    @end
    
    
    @implementation YYImageCache
    
    /**
     *  图片消耗
     */
    - (NSUInteger)imageCost:(UIImage *)image {
        CGImageRef cgImage = image.CGImage;
        if (!cgImage) return 1;
        CGFloat height = CGImageGetHeight(cgImage);
        size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
        NSUInteger cost = bytesPerRow * height;
        if (cost == 0) cost = 1;
        return cost;
    }
    
    /**
     *  通过data转换为image
     */
    - (UIImage *)imageFromData:(NSData *)data {
        NSData *scaleData = [YYDiskCache getExtendedDataFromObject:data];
        CGFloat scale = 0;
        if (scaleData) {
            scale = ((NSNumber *)[NSKeyedUnarchiver unarchiveObjectWithData:scaleData]).doubleValue;
        }
        if (scale <= 0) scale = [UIScreen mainScreen].scale;
        UIImage *image;
        if (_allowAnimatedImage) {
            image = [[YYImage alloc] initWithData:data scale:scale];
            if (_decodeForDisplay) image = [image yy_imageByDecoded];
        } else {
            YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
            image = [decoder frameAtIndex:0 decodeForDisplay:_decodeForDisplay].image;
        }
        return image;
    }
    
    #pragma mark Public
    /**
     *  单例类的初始化方法
     */
    + (instancetype)sharedCache {
        static YYImageCache *cache = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                                       NSUserDomainMask, YES) firstObject];
            //拼接路径
            cachePath = [cachePath stringByAppendingPathComponent:@"com.ibireme.yykit"];
            cachePath = [cachePath stringByAppendingPathComponent:@"images"];
            cache = [[self alloc] initWithPath:cachePath];
        });
        return cache;
    }
    
    - (instancetype)init {
        @throw [NSException exceptionWithName:@"YYImageCache init error" reason:@"YYImageCache must be initialized with a path. Use 'initWithPath:' instead." userInfo:nil];
        return [self initWithPath:nil];
    }
    
    /**
     *  在初始化的时候同时初始化内存缓存跟磁盘缓存
     *
     */
    - (instancetype)initWithPath:(NSString *)path {
        //在调用父类init之前先初始化一个内存缓存跟磁盘缓存
        YYMemoryCache *memoryCache = [YYMemoryCache new];//生成内存缓存
        memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES;//内存警告的时候删除所有内容
        memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES;//进入后台删除所有内容
        memoryCache.countLimit = NSUIntegerMax;//不予限制
        memoryCache.costLimit = NSUIntegerMax;//不予限制
        memoryCache.ageLimit = 12 * 60 * 60;//cache存在的时间限制设置为12个小时
    
        YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];//生成磁盘缓存
        diskCache.customArchiveBlock = ^(id object) { return (NSData *)object; };//自己来archive数据
        diskCache.customUnarchiveBlock = ^(NSData *data) { return (id)data; };//自己unarchive数据
        if (!memoryCache || !diskCache) return nil;//如果有任意一个初始化失败,返回nil
    
        self = [super init];
        _memoryCache = memoryCache;
        _diskCache = diskCache;
        _allowAnimatedImage = YES;
        _decodeForDisplay = YES;
        return self;
    }
    
    - (void)setImage:(UIImage *)image forKey:(NSString *)key {
        [self setImage:image imageData:nil forKey:key withType:YYImageCacheTypeAll];
    }
    
    - (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type {
        //在每一个方法执行前先检查参数的有效性,非常好的习惯
        if (!key || (image == nil && imageData.length == 0)) return;
    
        __weak typeof(self) _self = self;
        //如果类型有YYImageCacheTypeMemory
        if (type & YYImageCacheTypeMemory) { // add to memory cache
            if (image) {
                if (image.yy_isDecodedForDisplay) {
                    //开启了位图解码的话直接把图片丢进内存缓存里面咯
                    [_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]];
                } else {
                    //否则开启一个异步的解码队列,把图片转成位图,再丢进缓存里面
                    dispatch_async(YYImageCacheDecodeQueue(), ^{
                        __strong typeof(_self) self = _self;
                        if (!self) return;
                        [self.memoryCache setObject:[image yy_imageByDecoded] forKey:key withCost:[self imageCost:image]];
                    });
                }
            } else if (imageData) {//如果图片不存在,图片数据存在,那就通过data生成一个图片,丢进内存中存起来
                dispatch_async(YYImageCacheDecodeQueue(), ^{
                    __strong typeof(_self) self = _self;
                    if (!self) return;
                    UIImage *newImage = [self imageFromData:imageData];
                    [self.memoryCache setObject:[self imageFromData:imageData] forKey:key withCost:[self imageCost:newImage]];
                });
            }
        }
        //如果类型包含磁盘缓存,存进磁盘
        if (type & YYImageCacheTypeDisk) { // add to disk cache
            if (imageData) {
                if (image) {
                    [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:imageData];
                }
                [_diskCache setObject:imageData forKey:key];
            } else if (image) {
                dispatch_async(YYImageCacheIOQueue(), ^{
                    __strong typeof(_self) self = _self;
                    if (!self) return;
                    NSData *data = [image yy_imageDataRepresentation];
                    [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data];
                    [self.diskCache setObject:data forKey:key];
                });
            }
        }
    }
    
    /**
     *  全删咯
     *
     */
    - (void)removeImageForKey:(NSString *)key {
        [self removeImageForKey:key withType:YYImageCacheTypeAll];
    }
    //有哪个类型删哪个
    - (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type {
        if (type & YYImageCacheTypeMemory) [_memoryCache removeObjectForKey:key];
        if (type & YYImageCacheTypeDisk) [_diskCache removeObjectForKey:key];
    }
    
    - (BOOL)containsImageForKey:(NSString *)key {
        return [self containsImageForKey:key withType:YYImageCacheTypeAll];
    }
    
    - (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type {
        if (type & YYImageCacheTypeMemory) {
            if ([_memoryCache containsObjectForKey:key]) return YES;
        }
        if (type & YYImageCacheTypeDisk) {
            if ([_diskCache containsObjectForKey:key]) return YES;
        }
        return NO;
    }
    
    - (UIImage *)getImageForKey:(NSString *)key {
        return [self getImageForKey:key withType:YYImageCacheTypeAll];
    }
    
    //通过key找图片,都比较简单
    - (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type {
        if (!key) return nil;
        if (type & YYImageCacheTypeMemory) {
            UIImage *image = [_memoryCache objectForKey:key];
            if (image) return image;
        }
        if (type & YYImageCacheTypeDisk) {
            NSData *data = (id)[_diskCache objectForKey:key];
            UIImage *image = [self imageFromData:data];
            if (image && (type & YYImageCacheTypeMemory)) {
                [_memoryCache setObject:image forKey:key withCost:[self imageCost:image]];
            }
            return image;
        }
        return nil;
    }
    
    //跟上个方法类似,只不过把查询的结果通过block传递了回去
    - (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void (^)(UIImage *image, YYImageCacheType type))block {
        if (!block) return;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            UIImage *image = nil;
    
            if (type & YYImageCacheTypeMemory) {
                image = [_memoryCache objectForKey:key];
                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        block(image, YYImageCacheTypeMemory);
                    });
                    return;
                }
            }
    
            if (type & YYImageCacheTypeDisk) {
                NSData *data = (id)[_diskCache objectForKey:key];
                image = [self imageFromData:data];
                if (image) {
                    [_memoryCache setObject:image forKey:key];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        block(image, YYImageCacheTypeDisk);
                    });
                    return;
                }
            }
    
            dispatch_async(dispatch_get_main_queue(), ^{
                block(nil, YYImageCacheTypeNone);
            });
        });
    }
    
    - (NSData *)getImageDataForKey:(NSString *)key {
        return (id)[_diskCache objectForKey:key];
    }
    
    - (void)getImageDataForKey:(NSString *)key withBlock:(void (^)(NSData *imageData))block {
        if (!block) return;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSData *data = (id)[_diskCache objectForKey:key];
            dispatch_async(dispatch_get_main_queue(), ^{
                block(data);
            });
        });
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:YYWebImage源码解析

          本文链接:https://www.haomeiwen.com/subject/jgzfextx.html