iOS框架 - SDWebImage

·  阅读 1492

如何设计一个图片缓存框架?

可以模仿 SDWebImage 来实现。

  • 构成
    • Manager
    • 内存、磁盘缓存
    • 网络下载
    • Code Manager
      • 图片解码
      • 图片解压缩
  • 内存设计需要考虑的问题
    • 存储的 Size,因为内存的空间有限,我们针对不同尺寸的图片,给出不同的方案
      • 10K 以下的50个
      • 100Kb 以下的20个
      • 100kb 以上的10个
    • 淘汰的策略:内存的淘汰策略 采取 LRU(最近最少使用算法)
      • 定期检查(不建议,耗性能)
      • 提高检查触发频率(一定要注意开销)
        • 前后台切换的时候
        • 每次读写的时候
  • 磁盘设计需要考虑的问题
    • 存储方式
    • 大小限制(有固定的大小)
    • 移除策略(可以设置为7天或者15天)
  • 网络设计需要考虑的问题
    • 图片请求的最大并发量
    • 请求超时策略
    • 请求优先级
  • 图片解码
    • 应用 策略模式,针对 jpgpnggif 等不同的图片格式进行解码
    • 图片解码的时机
      • 在 子线程 图片刚下载完时
      • 在 子线程 刚从磁盘读取完时
      • 避免在主线程解压缩、解码,避免卡顿

SDWebImage解读

图片加载流程:

1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
4.下载后的图片会加入缓存中,并写入磁盘中
5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
复制代码

SDWebImage主要有以下类:

  • SDWebImageDownloader:图片下载管理类
  • SDImageCache: 图片缓存管理类
  • SDWebImageManager:核心类,持有SDWebImageDownloaderSDImageCache
  • SDWebImageCombinedOperation:从缓存加载图片操作类
  • SDWebImageDownloaderOperation:从服务端下载图片操作类
  • UIImageView+WebCache: 暴露接口,内部SDWebImageManager去加载图片

UIImageView+WebCache.m

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    /* 
     * 取消当前UIImageView的所有图片下载操作(runtime绑定的loadOperationKey字典下,查找key=UIImageViewImageLoad遵守SDWebImageCombinedOperation协议的操作,通过协议取消下载)
     */
    [self sd_cancelCurrentImageLoad];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

   	/*----
      省略 
    ----*/
    
    if (!url) {
        // completedBlock失败
        return;
    }  
    
    __weak __typeof(self)wself = self;
    /*
     * SDWebImageManager管理器进行图片加载(从内存、磁盘或服务器加载) 
     * 当内存或磁盘有缓存时,operation是SDWebImageCombinedOperation;当内存或磁盘没有缓存时(服务端加载),operation是SDWebImageDownloaderOperation;
     */
    id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { 
        dispatch_main_sync_safe(^{ 
            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);
            }
        });
    }];
    
    // 将operation下载操作放入loadOperationKey字典中
    [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
}
复制代码

SDWebImageManager.m

//////////////////////////////////////////////////
//负责管理cache,涉及内存缓存和硬盘保存
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//负责从网络下载图片
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader; 
// 
@property (strong, nonatomic) NSMutableArray <SDWebImageCombinedOperation *>*runningOperations;

//////////////////////////////////////////////////
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {   
    
    // 实例化一个下载操作
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    // 缓存无用的url,避免下次重复下载无用的图片路径                                      
    BOOL isFailedUrl = NO;
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }

    // 图片url为空或url是无效的(如404) 就不执行下载,直接return                                      
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });
        return operation;
    }

    // 下载操作operation放入到SDWebImageManager下的operation数组中                                       
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];
	
    /*
     * 根据key(图片url)尝试从缓存中加载图片
     * done回调中SDImageCacheType枚举:是磁盘还是内存中的图片数据
     */                            
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {  
        // 如果操作已经取消,移出操作元素直接返回
        if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }

            return;
        }
         
        // 如果内存或磁盘有图片,则回调缓存中的图片
        if (image) {
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !strongOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
            
            // 移除操作
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
        // 如果内存和磁盘都没有图片,就使用SDWebImageDownloader从服务器去下载
        else  {
         /*----
      	  SDWebImageDownloaderOptions downloaderOptions策略忽略 
    	  ---*/ 
            id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { 
                if (!strongOperation || strongOperation.isCancelled) {
                    // 加载图片取消,则什么也不做
                } else if (error) {
                   // 加载图片错误回调 
                }
                else { 
                    // 加载成功,存储图片到缓存(内存和磁盘)
                    [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }
                // 主线程回调
                dispatch_main_sync_safe(^{
                    if (strongOperation && !strongOperation.isCancelled) {
                        completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                    }
                });
            }
                                                        
            if (finished) {
                @synchronized (self.runningOperations) {
                    if (strongOperation) {
                        [self.runningOperations removeObject:strongOperation];
                    }
                }
             }                                         
         }]; 
        }  
    }];

    return operation;
}
复制代码

SDImageCache.m

//////////////////////////////////////////////////////////////////////
// 内存中图片缓存
@property (strong, nonatomic) NSCache *memCache;
// 磁盘读取图片队列
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;

//////////////////////////////////////////////////////////////////////
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
    /*省略*/

    // 首先尝试从内存中图片缓存(NSCache *memCache属性)
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        doneBlock(image, SDImageCacheTypeMemory);
        return nil;
    }

    // 内存中没有,就从磁盘中读取
    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            return;
        }

        @autoreleasepool {
            // 从磁盘中获取图片
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.shouldCacheImagesInMemory) {
                // 磁盘中有图片,就把图片放到内存cache中
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            // 磁盘中查询到图片回调
            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, SDImageCacheTypeDisk);
            });
        }
    });

    return operation;
}
复制代码

SDWebImageDownloader.m

//////////////////////////////////////////////////////////////////////
// 存储下载回调,key是图片url,value数组,数组有进度回调、完成下载回调等
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
// 下载队列
@property (strong, nonatomic) NSOperationQueue *downloadQueue;

//////////////////////////////////////////////////////////////////////
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageDownloaderOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageDownloaderCompletedBlock)completedBlock
{
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;

    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{ 
         
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:0 timeoutInterval:timeoutInterval];
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;  
        
        operation = [[wself.operationClass alloc] initWithRequest:request inSession:self.session options:options progress:nil
                     completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
            // 下载完成,回调
			for (NSDictionary *callbacks in callbacksForURL) {
            	SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
            	if (callback) callback(image, data, error, finished);
             }
        }
                     cancelled:nil]; 
        /*----
 		  省略
		----*/
        
        // operation放到队列中
        [wself.downloadQueue addOperation:operation]; 
    }];

    return operation;
}
复制代码
分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改