SDWebImage源码解析

在开发项目的过程中会用到很多第三方库,比如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设置图片的时候会调用方法: -

(void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;

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

SDWebImageOptions

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {    SDWebImageRetryFailed = 1

一般使用的是SDWebImageRetryFailed | SDWebImageLowPriority,

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

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

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

@property (strong, nonatomic, readwrite) SDImageCache *imageCache;//负责管理cache,涉及内存缓存和硬盘保存@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;//负责从网络下载图片@property (strong, nonatomic) NSMutableArray *runningOperations;//包含所有当前正在下载的操作对象

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

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

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

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

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

- (void)start {  /*...*/#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0        //开启后台下载        if ([self shouldContinueWhenAppEntersBackground]) {            __weak __typeof__ (self) wself = self;            self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{                __strong __typeof (wself) sself = wself;                if (sself) {                    [sself cancel];                    [[UIApplication sharedApplication] endBackgroundTask:sself.backgroundTaskId];                    sself.backgroundTaskId = UIBackgroundTaskInvalid;                }            }];        }#endif        self.executing = YES;        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];    }    [self.connection start];

    if (self.connection) {        if (self.progressBlock) {            self.progressBlock(0, NSURLResponseUnknownLength);        }       //在主线程发通知,这样也保证在主线程收到通知        dispatch_async(dispatch_get_main_queue(), ^{            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];        });       CFRunLoopRun();//在默认模式下运行当前runlooprun,直到调用CFRunLoopStop停止运行        if (!self.isFinished) {            [self.connection cancel];            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];        }    }#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];        self.backgroundTaskId = UIBackgroundTaskInvalid;    }#endif}

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

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;    @synchronized(self) {        CFRunLoopStop(CFRunLoopGetCurrent());//停止当前对runloop        /*...*/        if (completionBlock) {            /*...*/            UIImage *image = [UIImage sd_imageWithData:self.imageData];            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];            image = [self scaledImageForKey:key image:image];

              // Do not force decoding animated GIFs             if (!image.images) {                 if (self.shouldDecompressImages) {                    image = [UIImage decodedImageWithImage:image];//图片解码                }            }            if (CGSizeEqualToSize(image.size, CGSizeZero)) {                completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);            }            else {                completionBlock(image, self.imageData, nil, YES);            }        }    }    self.completionBlock = nil;    [self done];}

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

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

查询图片

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

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

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

- (UIImage *)diskImageForKey:(NSString *)key {    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];    if (data) {        UIImage *image = [UIImage sd_imageWithData:data];        image = [self scaledImageForKey:key image:image];        if (self.shouldDecompressImages) {            image = [UIImage decodedImageWithImage:image];        }        return image;    }    else {        return nil;    }}

保存图片

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

- (NSString *)cachedFileNameForKey:(NSString *)key {    //根据key生成对应的MD5值作为文件名    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]];

    return filename;}
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {    //保存到NSCache,cost为像素值    [self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale * image.scale];    if (toDisk) {        dispatch_async(self.ioQueue, ^{            NSData *data = imageData;            if (image && (recalculate || !data)) {               //判断图片格式                BOOL imageIsPng = YES;                // 查看imagedata的前缀是否是PNG的前缀格式                if ([imageData length] >= [kPNGSignatureData length]) {                    imageIsPng = ImageDataHasPNGPreffix(imageData);                }                if (imageIsPng) {                    data = UIImagePNGRepresentation(image);                }                else {                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);                }            }            if (data) {                if (![_fileManager fileExistsAtPath:_diskCachePath]) {                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];                }                //保存data到指定的路径中                [_fileManager createFileAtPath:[self defaultCachePathForKey:key] contents:data attributes:nil];            }        });    }}

硬盘文件的管理

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

  • 清除过期的文件,默认一星期
  • 如果设置了最大缓存,并且当前缓存的文件超过了这个限制,则删除最旧的文件,直到当前缓存文件的大小为最大缓存大小的一半
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {    dispatch_async(self.ioQueue, ^{        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

       // This enumerator prefetches useful properties for our cache files.        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL                                                   includingPropertiesForKeys:resourceKeys                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles                                                                 errorHandler:NULL];

       NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];        NSUInteger currentCacheSize = 0;

       // Enumerate all of the files in the cache directory.  This loop has two purposes:        //        //  1. Removing files that are older than the expiration date.        //  2. Storing file attributes for the size-based cleanup pass.        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];        for (NSURL *fileURL in fileEnumerator) {            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];

           // Skip directories.            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {                continue;            }

           // Remove files that are older than the expiration date;            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {                [urlsToDelete addObject:fileURL];                continue;            }

           // Store a reference to this file and account for its total size.            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];            [cacheFiles setObject:resourceValues forKey:fileURL];        }

       for (NSURL *fileURL in urlsToDelete) {            [_fileManager removeItemAtURL:fileURL error:nil];        }

       // If our remaining disk cache exceeds a configured maximum size, perform a second        // size-based cleanup pass.  We delete the oldest files first.        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {            // Target half of our maximum cache size for this cleanup pass.            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;

           // Sort the remaining cache files by their last modification time (oldest first).            NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent                                                            usingComparator:^NSComparisonResult(id obj1, id obj2) {                                                                return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];                                                            }];

           // Delete files until we fall below our desired cache size.            for (NSURL *fileURL in sortedFiles) {                if ([_fileManager removeItemAtURL:fileURL error:nil]) {                    NSDictionary *resourceValues = cacheFiles[fileURL];                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

                   if (currentCacheSize < desiredCacheSize) {                        break;                    }                }            }        }        if (completionBlock) {            dispatch_async(dispatch_get_main_queue(), ^{                completionBlock();            });        }    });}

总结

  • 接口设计简单
    通常我们使用较多的UIImageView分类:
[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]                placeholderImage:[UIImage imageNamed:@"placeholder"]];
  • 一个简单的接口将其中复杂的实现细节全部隐藏:简单就是美。
  • 采用NSCache作为内存缓
  • 耗时较长的请求,都采用异步形式,在回调函数块中处理请求结果
  • NSOperation和NSOperationQueue:可以取消任务处理队列中的任务,设置最大并发数,设置operation之间的依赖关系。
  • 图片缓存清理的策略
  • dispatch_barrier_sync:前面的任务执行结束后它才执行,而且它后面的任务要等它执行完成之后才会执行。
  • 使用weak self strong self 防止retain circle
  • 如果子线程进需要不断处理一些事件,那么设置一个Run Loop是最好的处理方式
时间: 2024-08-27 22:01:55

SDWebImage源码解析的相关文章

SDWebImage源码解析(二)

源码来源: https://github.com/rs/SDWebImage 版本: 3.7 SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能.它具有以下功能: 提供UIImageView的一个分类,以支持网络图片的加载与缓存管理 一个异步的图片加载器 一个异步的内存+磁盘图片缓存 支持GIF图片 支持WebP图片 后台图片解压缩处理 确保同一个URL的图片不被下载多次 确保虚假的URL不会被反复加载 确保下载及缓存时,主

SDWebImage源码解析之SDWebImageManager的注解

http://www.cocoachina.com/ios/20150612/12118.html 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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

【原】SDWebImage源码阅读(五)

[原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲解缓存.之前我们也遇到一些缓存的属性和方法,比如storeImage.queryDiskCacheForKey.memCache等等. SDWebImage的缓存分为两个部分,一个内存缓存,使用NSCache实现,另一个就是硬盘缓存(disk),使用NSFileManager实现. 不过这么多函数,

iOS开源库源码解析之SDWebImage

来自Leo的原创博客,转载请著名出处 我的stackoverflow 这个源码解析系列的文章 AsnycDispalyKit SDWebImage(本文) 前言 SDWebImage是iOS开发中十分流行的库,大多数的开发者在下载图片或者加载网络图片并且本地缓存的时候,都会用这个框架.这个框架相对来说,源代码还是比较少的.本文会详细的讲解这些类的架构关系和原理. 本文会先介绍类的整体架构关系,先有一个宏观的认识.然后讲解sd_setImageWithURL的加载逻辑,因为这是SDWebImage

iOS开源库源码解析之Mantle

来自Leo的原创博客,转载请著名出处 我的StackOverflow 这个源码解析系列的文章 AsnycDispalyKit SDWebImage Mantle(本文) AFNetworking(3.0) MBProgressHud SwiftJSON MagicRecord Alamofire 前言 iOS开发中,不管是哪种设计模式,Model层都是不可或缺的.而Model层的第三方库常用的库有以下几个 JSONModel Mantle MJExtension JSON data到对象的转换原

SDWebImage源码阅读-第三篇

这一篇讲讲不常用的一些方法. 1 sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: completed: 取得上次缓存的图片,然后作为占位图的参数再次进行一次图片设置. - (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options

SDWebImage源码剖析(一)

  在开发项目的过程中会用到很多第三方库,比如AFNetWorking,SDWebImage,FMDB等,但一直都没去好好的研究一下,最近刚好项目不是太紧,闲下来可以给自己充充电,先研究一下SDWebImage的底层实现,源码地址:SDWebImage  先介绍一下SDWebImage,我们使用较多的是它提供的UIImageView分类,支持从远程服务器下载并缓存图片.自从iOS5.0开始,NSURLCache也可以处理磁盘缓存,那么SDWebImage的优势在哪?首先NSURLCache是缓存

美女图片采集器 (源码+解析)

前言: 有一段时间没写博客了, "持之以恒"徽章都暗了, 实在不该. 前一段确实比较忙, ...小小地给自己的懒找个借口吧. 大二即将结束, 学习iOS也有一段时间了.今天抽点时间, 开源一个前几天刚上传的App里面的一个功能, RT, 美女图片采集器.   美女.. 相信没有人不喜欢吧, 基于此, 这个小Demo应运而生. 效果演示: 看到这里, 如果还有兴趣学习的话, 可以先到我的git中下载源码, 然后配合着源码看我下面的解析.相信, 会让你有所收获的. git下载链接: Bea

SDWebImage源码学习笔记

//  这是我第二次学习sdwebimage源码,第一次学习吸收的很少,看不懂啊.第二次看个50%,在此记录一点笔记. 首先是目录: 1.SDWebImage目录 里面有两个类,SDWebImageCompat.h 里面有个根据屏幕设置图片scale的方法 SDWebImageOperation.h 声明了一个协议,取消操作 (可以理解这一个放的公共方法目录) 2.Downloader 目录(顾名思义,下载操作相关的目录)里面有两个关键的类 SDWebImageDownloaderOperati