分享

SDWebImage源码解析

 没原创_去搜索 2015-08-06
http://my.oschina.net/sayonala/blog/468238

http://blog.csdn.net/dean19900504/article/details/41909465



http://www./a/1402505.html



===================


摘要 从网上看到的,讲的不错 ,留存

在开发项目的过程中会用到很多第三方库,比如AFNetWorking,SDWebImage,FMDB等,但一直都没去好好的研究一下,最近刚好项目不是太紧,闲下来可以给自己充充电,先研究一下SDWebImage的底层实现,源码地址:SDWebImage 

先介绍一下SDWebImage,我们使用较多的是它提供的UIImageView分类,支持从远程服务器下载并缓存图片。自从iOS5.0开始,NSURLCache也可以处理磁盘缓存,那么SDWebImage的优势在哪?首先NSURLCache是缓存原始数据(raw data)到磁盘或内存,因此每次使用的时候需要将原始数据转换成具体的对象,如UIImage等,这会导致额外的数据解析以及内存占用等,而SDWebImage则是缓存UIImage对象在内存,缓存在NSCache中,同时直接保存压缩过的图片到磁盘中;还有一个问题是当你第一次在UIImageView中使用image对象的时候,图片的解码是在主线程中运行的!而SDWebImage会强制将解码操作放到子线程中。下图是SDWebImage简单的类图关系:

下面从UIImageView的图片加载开始看起,Let's go! 首先我们在给UIImageView设置图片的时候会调用方法: -

1
<span style="font-size: 14px;">(void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;<br></span>

其中url为远程图片的地址,而placeholder为预显示的图片。 其实还可以添加一些额外的参数,比如图片选项

1
<span style="font-size: 14px;">SDWebImageOptions<br><br>typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {<br>    SDWebImageRetryFailed = 1<br></span>

 一般使用的是SDWebImageRetryFailed | SDWebImageLowPriority,

下面看看具体的函数调用:

1
<span style="font-size: 14px;">- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock <br>{<br>    [self sd_cancelCurrentImageLoad];//取消正在下载的操作<br>    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//关联该view对应的图片URL  <br>   /*...*/ <br>    if (url) {<br>        __weak UIImageView *wself = self;//防止retain cricle<br>        //由SDWebImageManager负责图片的获取<br>        id  operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {<br>              /*获取图片到主线层显示*/ <br>        }];<br>        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];<br>    } <br>}<br></span>

 

可以看出图片是从服务端、内存或者硬盘获取是由SDWebImageManager管理的,这个类有几个重要的属性: 

1
<span style="font-size: 14px;">@property (strong, nonatomic, readwrite) SDImageCache *imageCache;//负责管理cache,涉及内存缓存和硬盘保存<br>@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;//负责从网络下载图片<br>@property (strong, nonatomic) NSMutableArray *runningOperations;//包含所有当前正在下载的操作对象<br></span>

manager会根据URL先去imageCache中查找对应的图片,如果没有在使用downloader去下载,并在下载完成缓存图片到imageCache,接着看实现:

1
<span style="font-size: 14px;"> - (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options<br>                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock<br>                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock<br> {<br>     /*...*/<br>    //根据URL生成对应的key,没有特殊处理为[url absoluteString];<br>    NSString *key = [self cacheKeyForURL:url];<br>    //去imageCache中寻找图片<br>    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) <br>    {<br>       /*...*/<br>       //如果图片没有找到,或者采用的SDWebImageRefreshCached选项,则从网络下载<br>        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {<br>                dispatch_main_sync_safe(^{<br>                  //如果图片找到了,但是采用的SDWebImageRefreshCached选项,通知获取到了图片,并再次从网络下载,使NSURLCache重新刷新<br>                     completedBlock(image, nil, cacheType, YES, url);<br>                });<br>            }<br>            /*下载选项设置*/ <br>            //使用imageDownloader开启网络下载<br>            id  subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {<br>                /*...*/<br>               if (downloadedImage && finished) {<br>                     //下载完成后,先将图片保存到imageCache中,然后主线程返回<br>                     [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];<br>                        }<br>                     dispatch_main_sync_safe(^{<br>                            if (!weakOperation.isCancelled) {<br>                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);<br>                            }<br>                        });<br>                    }<br>                }<br>          /*...*/<br>       }<br>        else if (image) {<br>          //在cache中找到图片了,直接返回<br>            dispatch_main_sync_safe(^{<br>                if (!weakOperation.isCancelled) {<br>                    completedBlock(image, nil, cacheType, YES, url);<br>                }<br>            });<br>        }<br>    }];<br>    return operation;<br>}<br></span>


下面先看downloader从网络下载的过程,下载是放在NSOperationQueue中进行的,默认maxConcurrentOperationCount为6,timeout时间为15s: 

1
<span style="font-size: 14px;">- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {<br>    __block SDWebImageDownloaderOperation *operation;<br>    __weak SDWebImageDownloader *wself = self;<br>    /*...*/<br>    //防止NSURLCache和SDImageCache重复缓存<br>    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy :NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];<br>    request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);<br>    request.HTTPShouldUsePipelining = YES;<br>    request.allHTTPHeaderFields = wself.HTTPHeaders;//设置http头部<br>    //SDWebImageDownloaderOperation派生自NSOperation,负责图片下载工作<br>    operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request<br>                                                          options:options<br>                                                         progress:^(NSInteger receivedSize, NSInteger expectedSize) {}<br>                                                        completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {}<br>                                                        cancelled:^{}];<br>    operation.shouldDecompressImages = wself.shouldDecompressImages;//是否需要解码<br>    if (wself.username && wself.password) {<br>            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];<br>    }<br>    if (options & SDWebImageDownloaderHighPriority) {<br>            operation.queuePriority = NSOperationQueuePriorityHigh;<br>        } else if (options & SDWebImageDownloaderLowPriority) {<br>            operation.queuePriority = NSOperationQueuePriorityLow;<br>    }<br>        [wself.downloadQueue addOperation:operation];<br>        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {<br>            // 如果下载顺序是后面添加的先运行<br>            [wself.lastAddedOperation addDependency:operation];<br>            wself.lastAddedOperation = operation;<br>        }<br>    }];<br>    return operation;<br>}<br></span>

SDWebImageDownloaderOperation派生自NSOperation,通过NSURLConnection进行图片的下载,为了确保能够处理下载的数据,需要在后台运行runloop:

1
<span style="font-size: 14px;">- (void)start {<br>  /*...*/<br>#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0<br>        //开启后台下载<br>        if ([self shouldContinueWhenAppEntersBackground]) {<br>            __weak __typeof__ (self) wself = self;<br>            self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{<br>                __strong __typeof (wself) sself = wself;<br>                if (sself) {<br>                    [sself cancel];<br>                    [[UIApplication sharedApplication] endBackgroundTask:sself.backgroundTaskId];<br>                    sself.backgroundTaskId = UIBackgroundTaskInvalid;<br>                }<br>            }];<br>        }<br>#endif<br>        self.executing = YES;<br>        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];<br>    }<br>    [self.connection start];<br><br>    if (self.connection) {<br>        if (self.progressBlock) {<br>            self.progressBlock(0, NSURLResponseUnknownLength);<br>        }<br>       //在主线程发通知,这样也保证在主线程收到通知<br>        dispatch_async(dispatch_get_main_queue(), ^{<br>            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];<br>        });<br>       CFRunLoopRun();//在默认模式下运行当前runlooprun,直到调用CFRunLoopStop停止运行<br>        if (!self.isFinished) {<br>            [self.connection cancel];<br>            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];<br>        }<br>    }<br>#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0<br>    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {<br>        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];<br>        self.backgroundTaskId = UIBackgroundTaskInvalid;<br>    }<br>#endif<br>}<br></span>

下载过程中,在代理 - (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data中将接收到的数据保存到NSMutableData中,[self.imageData appendData:data],下载完成后在该线程完成图片的解码,并在完成的completionBlock中进行imageCache的缓存:

1
<span style="font-size: 14px;">- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {<br>    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;<br>    @synchronized(self) {<br>        CFRunLoopStop(CFRunLoopGetCurrent());//停止当前对runloop<br>        /*...*/<br>        if (completionBlock) {<br>            /*...*/<br>            UIImage *image = [UIImage sd_imageWithData:self.imageData];<br>            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];<br>            image = [self scaledImageForKey:key image:image];<br><br>              // Do not force decoding animated GIFs<br>             if (!image.images) {<br>                 if (self.shouldDecompressImages) {<br>                    image = [UIImage decodedImageWithImage:image];//图片解码<br>                }<br>            }<br>            if (CGSizeEqualToSize(image.size, CGSizeZero)) {<br>                completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);<br>            }<br>            else {<br>                completionBlock(image, self.imageData, nil, YES);<br>            }<br>        }<br>    }<br>    self.completionBlock = nil;<br>    [self done];<br>}<br></span>

SDWebImageCache管理着SDWebImage的缓存,其中内存缓存采用NSCache,同时会创建一个ioQueue负责对硬盘的读写,并且会添加观察者,在收到内存警告、关闭或进入后台时完成对应的处理:

1
<span style="font-size: 14px;">- (id)init {<br>     _memCache = [[NSCache alloc] init];<br>     _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);<br>     //收到内存警告时,清除NSCache:[self.memCache removeAllObjects];<br>     [[NSNotificationCenter defaultCenter] addObserver:self<br>                                                 selector:@selector(clearMemory)<br>                                              name:UIApplicationDidReceiveMemoryWarningNotification<br>                                                   object:nil];<br>      //程序关闭时,会对硬盘文件做一些处理<br>      [[NSNotificationCenter defaultCenter] addObserver:self<br>                                                 selector:@selector(cleanDisk)<br>                                                     name:UIApplicationWillTerminateNotification<br>                                                   object:nil];<br>      //程序进入后台时,也会进行硬盘文件处理<br>      [[NSNotificationCenter defaultCenter] addObserver:self<br>                                                 selector:@selector(backgroundCleanDisk)<br>                                                     name:UIApplicationDidEnterBackgroundNotification<br>                                                   object:nil];<br>}<br></span>

查询图片

每次向SDWebImageCache索取图片的时候,会先根据图片URL对应的key值先检查内存中是否有对应的图片,如果有则直接返回;如果没有则在ioQueue中去硬盘中查找,其中文件名是是根据URL生成的MD5值,找到之后先将图片缓存在内存中,然后在把图片返回:

1
<span style="font-size: 14px;">- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {<br>    /*...*/<br>    // 首先查找内存缓存<br>    UIImage *image = [self imageFromMemoryCacheForKey:key];<br>    if (image) {<br>        doneBlock(image, SDImageCacheTypeMemory);<br>        return nil;<br>    }<br>    //硬盘查找<br>    NSOperation *operation = [NSOperation new];<br>    dispatch_async(self.ioQueue, ^{<br>        //创建自动释放池,内存及时释放<br>        @autoreleasepool {<br>            UIImage *diskImage = [self diskImageForKey:key];<br>            if (diskImage) {<br>                CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale * diskImage.scale;<br>                //缓存到NSCache中<br>                [self.memCache setObject:diskImage forKey:key cost:cost];<br>            }<br>            dispatch_async(dispatch_get_main_queue(), ^{<br>                doneBlock(diskImage, SDImageCacheTypeDisk);<br>            });<br>        }<br>    });<br>    return operation;<br>}<br></span>

在硬盘查询的时候,会在后台将NSData转成UIImage,并完成相关的解码工作:

1
<span style="font-size: 14px;">- (UIImage *)diskImageForKey:(NSString *)key {<br>    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];<br>    if (data) {<br>        UIImage *image = [UIImage sd_imageWithData:data];<br>        image = [self scaledImageForKey:key image:image];<br>        if (self.shouldDecompressImages) {<br>            image = [UIImage decodedImageWithImage:image];<br>        }<br>        return image;<br>    }<br>    else {<br>        return nil;<br>    }<br>}<br></span>

保存图片

当下载完图片后,会先将图片保存到NSCache中,并把图片像素大小作为该对象的cost值,同时如果需要保存到硬盘,会先判断图片的格式,PNG或者JPEG,并保存对应的NSData到缓存路径中,文件名为URL的MD5值:

1
<span style="font-size: 14px;">- (NSString *)cachedFileNameForKey:(NSString *)key {<br>    //根据key生成对应的MD5值作为文件名<br>    const char *str = [key UTF8String];<br>    if (str == NULL) {<br>        str = "";<br>    }<br>    unsigned char r[CC_MD5_DIGEST_LENGTH];<br>    CC_MD5(str, (CC_LONG)strlen(str), r);<br>    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",<br>                                                    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]];<br><br>    return filename;<br>}<br></span>
1
<span style="font-size: 14px;">- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk <br>{<br>    //保存到NSCache,cost为像素值<br>    [self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale * image.scale];<br>    if (toDisk) {<br>        dispatch_async(self.ioQueue, ^{<br>            NSData *data = imageData;<br>            if (image && (recalculate || !data)) {<br>               //判断图片格式<br>                BOOL imageIsPng = YES;<br>                // 查看imagedata的前缀是否是PNG的前缀格式<br>                if ([imageData length] >= [kPNGSignatureData length]) {<br>                    imageIsPng = ImageDataHasPNGPreffix(imageData);<br>                }<br>                if (imageIsPng) {<br>                    data = UIImagePNGRepresentation(image);<br>                }<br>                else {<br>                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);<br>                }<br>            }<br>            if (data) {<br>                if (![_fileManager fileExistsAtPath:_diskCachePath]) {<br>                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];<br>                }<br>                //保存data到指定的路径中<br>                [_fileManager createFileAtPath:[self defaultCachePathForKey:key] contents:data attributes:nil];<br>            }<br>        });<br>    }<br>}<br></span>

硬盘文件的管理

在程序退出或者进入后台时,会出图片文件进行管理,具体的策略:

  • 清除过期的文件,默认一星期 

  • 如果设置了最大缓存,并且当前缓存的文件超过了这个限制,则删除最旧的文件,直到当前缓存文件的大小为最大缓存大小的一半

1
<span style="font-size: 14px;">- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {<br>    dispatch_async(self.ioQueue, ^{<br>        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];<br>        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];<br><br>        // This enumerator prefetches useful properties for our cache files.<br>        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL<br>                                                   includingPropertiesForKeys:resourceKeys<br>                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles<br>                                                                 errorHandler:NULL];<br><br>        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];<br>        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];<br>        NSUInteger currentCacheSize = 0;<br><br>        // Enumerate all of the files in the cache directory.  This loop has two purposes:<br>        //<br>        //  1. Removing files that are older than the expiration date.<br>        //  2. Storing file attributes for the size-based cleanup pass.<br>        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];<br>        for (NSURL *fileURL in fileEnumerator) {<br>            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];<br><br>            // Skip directories.<br>            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {<br>                continue;<br>            }<br><br>            // Remove files that are older than the expiration date;<br>            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];<br>            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {<br>                [urlsToDelete addObject:fileURL];<br>                continue;<br>            }<br><br>            // Store a reference to this file and account for its total size.<br>            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];<br>            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];<br>            [cacheFiles setObject:resourceValues forKey:fileURL];<br>        }<br><br>        for (NSURL *fileURL in urlsToDelete) {<br>            [_fileManager removeItemAtURL:fileURL error:nil];<br>        }<br><br>        // If our remaining disk cache exceeds a configured maximum size, perform a second<br>        // size-based cleanup pass.  We delete the oldest files first.<br>        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {<br>            // Target half of our maximum cache size for this cleanup pass.<br>            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;<br><br>            // Sort the remaining cache files by their last modification time (oldest first).<br>            NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent<br>                                                            usingComparator:^NSComparisonResult(id obj1, id obj2) {<br>                                                                return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];<br>                                                            }];<br><br>            // Delete files until we fall below our desired cache size.<br>            for (NSURL *fileURL in sortedFiles) {<br>                if ([_fileManager removeItemAtURL:fileURL error:nil]) {<br>                    NSDictionary *resourceValues = cacheFiles[fileURL];<br>                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];<br>                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];<br><br>                    if (currentCacheSize < desiredCacheSize) {<br>                        break;<br>                    }<br>                }<br>            }<br>        }<br>        if (completionBlock) {<br>            dispatch_async(dispatch_get_main_queue(), ^{<br>                completionBlock();<br>            });<br>        }<br>    });<br>}<br></span>

总结

  • 接口设计简单
    通常我们使用较多的UIImageView分类:

1
<span style="font-size: 14px;">[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]<br>                placeholderImage:[UIImage imageNamed:@"placeholder"]];<br></span>
  • 一个简单的接口将其中复杂的实现细节全部隐藏:简单就是美。

  • 采用NSCache作为内存缓

  • 耗时较长的请求,都采用异步形式,在回调函数块中处理请求结果

  • NSOperation和NSOperationQueue:可以取消任务处理队列中的任务,设置最大并发数,设置operation之间的依赖关系。

  • 图片缓存清理的策略

  • dispatch_barrier_sync:前面的任务执行结束后它才执行,而且它后面的任务要等它执行完成之后才会执行。

  • 使用weak self strong self 防止retain circle

  • 如果子线程进需要不断处理一些事件,那么设置一个Run Loop是最好的处理方式


分享到: 0




======================




我之前写过一篇博客,介绍缓存处理的三种方式,其中最难,最麻烦,最占内存资源的还是图片缓存,最近做的项目有大量的图片处理,还是采用了SDWebImage来处理,但是发现之前封装好的代码报错了。研究发现,是我用了新版的SDWebImage,好多方法都变了。

现在把代码贴出来,供大家参考。尤其是新手,看完这篇博客,图片缓存so easy。最后有demo供大家下载,先学习。

第一步,下载SDWebImage,导入工程。github托管地址https://github.com/rs/SDWebImage

第二步,在需要的地方导入头文件

1
#import "UIImageView+WebCache.h"

第三步,调用sd_setImageWithURL:方法缓存图片,注意,这就是新版本的新方法,旧方法是setImageWithURL:。下面将几个方法都介绍一下。

1. sd_setImageWithURL:

1
2
//图片缓存的基本代码,就是这么简单
    [self.image1 sd_setImageWithURL:imagePath1];

2. sd_setImageWithURL:  completed:

1
2
3
4
5
6
//用block 可以在图片加载完成之后做些事情
    [self.image2 sd_setImageWithURL:imagePath2 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
         
        NSLog(@"这里可以在图片加载完成之后做些事情");
         
    }];

3. sd_setImageWithURL:  placeholderImage:

1
2
//给一张默认图片,先使用默认图片,当图片加载完成后再替换
    [self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"]];

4. sd_setImageWithURL:  placeholderImage:  completed:

1
2
3
4
5
6
//使用默认图片,而且用block 在完成后做一些事情
    [self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
         
        NSLog(@"图片加载完成后做的事情");
         
    }];

5. sd_setImageWithURL:  placeholderImage:  options:

1
2
3
//options 选择方式
     
    [self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] options:SDWebImageRetryFailed];

其他就不一一介绍了,oc是自文档语言,看方法名就知道干什么的了。除了带options选项的方法,其他的方法都是综合存储,也就是内存缓存和磁盘缓存结合的方式,如果你只需要内存缓存,那么在options这里选择SDWebImageCacheMemoryOnly就可以了。

如果不想深入了解,到这里你已经可以用SDWebimage进行图片缓存了,接下来我要解释options的所有选项,以及SDWebImage内部执行流程。

一、options所有选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  //失败后重试
     SDWebImageRetryFailed = 1 << 0,
      
     //UI交互期间开始下载,导致延迟下载比如UIScrollView减速。
     SDWebImageLowPriority = 1 << 1,
      
     //只进行内存缓存
     SDWebImageCacheMemoryOnly = 1 << 2,
      
     //这个标志可以渐进式下载,显示的图像是逐步在下载
     SDWebImageProgressiveDownload = 1 << 3,
      
     //刷新缓存
     SDWebImageRefreshCached = 1 << 4,
      
     //后台下载
     SDWebImageContinueInBackground = 1 << 5,
      
     //NSMutableURLRequest.HTTPShouldHandleCookies = YES;
      
     SDWebImageHandleCookies = 1 << 6,
      
     //允许使用无效的SSL证书
     //SDWebImageAllowInvalidSSLCertificates = 1 << 7,
      
     //优先下载
     SDWebImageHighPriority = 1 << 8,
      
     //延迟占位符
     SDWebImageDelayPlaceholder = 1 << 9,
      
     //改变动画形象
     SDWebImageTransformAnimatedImage = 1 << 10,

二、SDWebImage内部实现过程

  1. 入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。

  2. 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

  3. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。

  4. SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。

  5. 如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。

  6. 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。

  7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

  8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。

  9. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。

  10. 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

  11. connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。

  12. connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

  13. 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

  14. 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。

  15. imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

  16. 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

  17. 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

  18. SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。

  19. SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。

  20. SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

从上面流程可以看出,当你调用setImageWithURL:方法的时候,他会自动去给你干这么多事,当你需要在某一具体时刻做事情的时候,你可以覆盖这些方法。比如在下载某个图片的过程中要响应一个事件,就覆盖这个方法:

1
2
3
4
5
6
7
8
9
10
11
//覆盖方法,指哪打哪,这个方法是下载imagePath2的时候响应
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
     
    [manager downloadImageWithURL:imagePath2 options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
         
        NSLog(@"显示当前进度");
         
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
         
        NSLog(@"下载完成");
    }];

对于初级来说,用sd_setImageWithURL:的若干个方法就可以实现很好的图片缓存。

Demo下载


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多