SDWebImage源代码梳理1#初始搭建

一步一步拆解SDWebImage SDK。

今天只涉及两个主要的类:

  • BJCAImageCache
  • BJCAWebImageView
//BJCAImageCache.h文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BJCAImageCache : NSObject

{
    NSMutableDictionary *cache;
    NSString *diskCachePath;
}

+(BJCAImageCache *)sharedImageCache;
//存储图片
-(void)storeImage:(UIImage *)image forKey:(NSString *)key;
-(void)storeImage:(UIImage *)image foeKey:(NSString *)key toDisk:(BOOL)toDisk;
//获取图片的方法
-(UIImage *)imageForKey:(NSString *)key;
-(UIImage *)imageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
//删除图片的方法
-(void)removeImageFrokey:(NSString *)key;

-(void)clearMemory;
-(void)clearDisk;
-(void)cleanDisk;

@end

NS_ASSUME_NONNULL_END
//BJCAImageCache.m文件
#import "BJCAImageCache.h"
/**
 常用的摘要算法,比如MD5,SHA1等。
 摘要算法就是,一种能产生特殊输出格式的算法,无论内容长度是多少,最后输出的都是同样的长度。
 */
#import <CommonCrypto/CommonDigest.h>
/**
 定义一个星期的时间;
 用static定义局部变量为静态变量,在函数调用结束之后不释放继续保留值。
 */
static NSInteger kMaxCacheAge = 60 * 60 * 24 * 7;
static BJCAImageCache *instance;

@implementation BJCAImageCache

#pragma mark NSObject
-(instancetype)init
{
    if (self = [super init])
    {
        cache = [[NSMutableDictionary alloc] init];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
         selector:@selector(didReceiveMemoryWarning)
         name:UIApplicationDidReceiveMemoryWarningNotification
         object:nil];
        //应用终止的通知,在应用在前台,双击home键,终止应用的时候会调用,单击home键回到桌面不会调用
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(willTerminate)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
        
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        diskCachePath = [paths[0] stringByAppendingPathComponent:@"ImageCache"];
        if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath])
        {
            //创建目录
            [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil];
        }
    }
    
    return self;
}

-(void)dealloc
{
    //不要写超类的方法
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
}

/**
 程序将要终止的时候,将磁盘清空
 */
-(void)willTerminate
{
    [self cleanDisk];
}

/**
 收到了内存警告*/
-(void)didReciveMemoryWaring
{
    [self clearMemory];
}

#pragma mark ImageCache (private)
-(NSString *)cachePathForKey:(NSString *)key
{
    //const char 是表示常量型的字符
    const char *str = [key UTF8String];
    //表示无符号的字符类型
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    //MD5加密
    CC_MD5(str, strlen(str), r);
    //x表示以十六进制形式输出,02表示不足两位前面补0,超过两位不影响。
    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 [diskCachePath stringByAppendingPathComponent:fileName];
}

#pragma mark ImageCache
+(BJCAImageCache *)sharedImageCache
{
    if (instance == nil)
    {
        instance = [[BJCAImageCache alloc] init];
    }
    return instance;
}

-(void)storeImage:(UIImage *)image forKey:(NSString *)key
{
    [self storeImage:image foeKey:key toDisk:YES];
}

-(void)storeImage:(UIImage *)image foeKey:(NSString *)key toDisk:(BOOL)toDisk
{
    if (image == nil)
    {
        return;
    }
    [cache setObject:image forKey:key];
    
    if (toDisk)
    {
        /**
         iOS中有两种转化图片的简单方法:
         1、UIImageJPEGRepresentation 图片、压缩系数。
         压缩后图片较小,图片质量也无较大差异,日常中主要用这个方法
         2、UIImagePNGRepresentation 图片
         压缩图片的图片较大
         */
        [[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, 1.0) attributes:nil];
    }
}

-(UIImage *)imageForKey:(NSString *)key
{
    return [self imageForKey:key fromDisk:YES];
}

-(UIImage *)imageForKey:(NSString *)key fromDisk:(BOOL)fromDisk
{
    UIImage *image = [cache objectForKey:key];
    if (!image && fromDisk)
    {
        image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:[self cachePathForKey:key]]];
        if (image != nil)
        {
            [cache setObject:image forKey:key];
        }
    }
    return image;
}

-(void)removeImageFrokey:(NSString *)key
{
    [cache removeObjectForKey:key];
    [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil];
}

-(void)clearMemory
{
    [cache removeAllObjects];
}

-(void)clearDisk
{
    [[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil];
    [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil];
}

-(void)cleanDisk
{
    //从现在开始的-7天
    NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-kMaxCacheAge];
    NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:diskCachePath];
    //可以枚举指定目录中的每个文件
    for (NSString *fileName in fileEnumerator)
    {
        NSString *filePath = [diskCachePath stringByAppendingFormat:fileName];
        //获取文件的大小。创建时间等属性。
        NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
        /**
         获取文件的修改时间;
         获取两个时间中较晚的那个时间;
         */
        if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate])
        {
            [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
        }
    }
}

@end
//BJCAWebImageView.h文件
#import <UIKit/UIKit.h>

@class BJCAWebImageDownloadOperation;

NS_ASSUME_NONNULL_BEGIN

@interface BJCAWebImageView : UIImageView

{
    UIImage *placeHolderImage;
    BJCAWebImageDownloadOperation *currentOperation;
}

-(void)setImageWithURL:(NSURL *)url;
-(void)downloadFinishedWithImage:(UIImage *)image;

@end

@interface BJCAWebImageDownloadOperation : NSOperation

{
    NSURL *url;
    BJCAWebImageView *delegate;
}

@property (nonatomic, strong) NSURL *url;
@property (nonatomic, weak) BJCAWebImageView *delegate;

-(id)initWithURL:(NSURL *)url delegate:(BJCAWebImageView *)delegate;

@end

NS_ASSUME_NONNULL_END
//BJCAWebImageView.m文件
#import "BJCAWebImageView.h"
#import "BJCAImageCache.h"

static NSOperationQueue *downloadQueue;
static NSOperationQueue *cacheInQueue;

@implementation BJCAWebImageView

#pragma mark RemoteImageView
-(void)setImageWithURL:(NSURL *)url
{
    if (currentOperation != nil)
    {
        [currentOperation cancel];//从队列中删除
        currentOperation = nil;
    }
    //保存占位图图像,以便在视图被重用的时候重新应用占位图
    if (placeHolderImage == nil)
    {
        placeHolderImage = self.image;
    }
    else
    {
        self.image = placeHolderImage;
    }
    //完整的url字符串当做key
    UIImage *cachedImage = [[BJCAImageCache sharedImageCache] imageFromKey:[url absoluteString]];
    if (cachedImage)
    {
        self.image = cachedImage;
    }
    else
    {
        if (downloadQueue == nil)
        {
            downloadQueue = [[NSOperationQueue alloc] init];
            [downloadQueue setMaxConcurrentOperationCount:8];
        }
        currentOperation = [[BJCAWebImageDownloadOperation alloc] initWithURL:url delegate:self];
        [downloadQueue addOperation:currentOperation];
    }
}

-(void)downloadFinishedWithImage:(UIImage *)image
{
    self.image = image;
    currentOperation = nil;
}

@end

@implementation BJCAWebImageDownloadOperation

@synthesize url, delegate;

-(id)initWithURL:(NSURL *)url delegate:(BJCAWebImageView *)delegate
{
    if (self = [super init])
    {
        self.url = url;
        self.delegate = delegate;
    }
    return self;
}

/**
 NSOperation有两个方法,main()和start()。如果想使用同步,就把逻辑写在main方法中,
 如果想使用异步,就写在start方法中。
 */
-(void)main
{
    if (self.isCancelled)
    {
        return;
    }
    NSData *data = [[NSData alloc] initWithContentsOfURL:url];
    UIImage *image = [[UIImage alloc] initWithData:data];
    
    if (!self.isCancelled)
    {
        [delegate performSelectorOnMainThread:@selector(downloadFinishedWithImage:) withObject:image waitUntilDone:YES];
    }
    
    if (cacheInQueue == nil)
    {
        cacheInQueue = [[NSOperationQueue alloc] init];
        [cacheInQueue setMaxConcurrentOperationCount:2];
    }
    
    NSString *cacheKey = [url absoluteString];
    BJCAImageCache *imageCache = [BJCAImageCache sharedImageCache];
    //现在将图片写入缓存中,不需要等待缓存写入操作队列完成
    [imageCache storeImage:image foeKey:cacheKey toDisk:NO];
    //将下一个缓存操作设置成命令对象,以避免影响下一个下载错误
    NSInvocation *cacheInINvocation = [NSInvocation invocationWithMethodSignature:[[imageCache class] instanceMethodSignatureForSelector:@selector(storeImage:forKey:)]];
    [cacheInINvocation setTarget:imageCache];
    [cacheInINvocation setSelector:@selector(storeImage:forKey:)];
    [cacheInINvocation setArgument:&image atIndex:2];
    [cacheInINvocation setArgument:&cacheKey atIndex:3];
    [cacheInINvocation retainArguments];
    NSInvocationOperation *cacheInOperation = [[NSInvocationOperation alloc] initWithInvocation:cacheInINvocation];
    
    [cacheInQueue addOperation:cacheInOperation];
}

@end

猜你喜欢

转载自blog.csdn.net/run_in_road/article/details/113845922
今日推荐