模仿SDWebImage的實現思路及代碼

一、主體架構

整體架構類似于MVC,UIImageView需要圖片從而向控制器請求數據,圖像下載管理器承擔了控制器的責任,里面包含有全局下載隊列,操作緩存等,負責調度下載,進一步向具體下載操作請求數據。圖像下載操作負責執行真正的下載任務,之后返回下載好的圖片給控制器。

screenshot.png

1)UIImageView實例在視圖控制器中調用方法setImageWithURLString:(NSString*)URLString來設置圖片。在該方法中首先取消掉當前正在執行的下載,否則多個下載任務(完成時間的先后不可控)會導致圖片錯亂。為了保證這個效果,需要有一個屬性來保存當前正在下載的URLString。而為了保證調用的方便,setImageWithURLString是UIImageView通過添加分類實現的,因此,需要通過runtime在運行時動態的添加一個屬性來保存當前正在下載的URLString。

2)DownloadImageManager,下載管理器是一個單例,從而保證其中的下載隊列等屬性不會被銷毀,從而建立緩存。里面有3個核心屬性,分別是1、操作緩存,以URLString為key,以下載操作為value,從而避免下載緩慢的時候發生重復請求。2、全局隊列,使用全局隊列可以控制線程的并發數。3、圖片內存緩存(沙盒緩存),通過緩存不用每次都通過網絡請求來獲取圖片,節省流量,加快速度。

3)圖像下載操作:繼承于NSOperation,通過繼承從而重寫main方法,在main方法中通過判斷cancle標志從而實現中斷下載的功能。實現取消下載是保證圖片不會錯亂的核心。

具體實現代碼:
UIImageview分類:
UIImageView+XZHWebImage.m

#define XZHCurrentURLString @"currentURLStirng"

@interface UIImageView ()
/**
 *  記錄當前正在下載的URL
 */
@property (nonatomic,copy)NSString *currentURLStirng;
@end
@implementation UIImageView (XZHWebImage)
- (void)setImageWithURLString:(NSString *)URLString{
    //如果對同一個cell執行了兩次請求操作,取消掉當前正在執行的
    if (![URLString isEqualToString:self.currentURLStirng]&&self.currentURLStirng) {
        [[XZHDownloadImageManager sharedManager] cancelDownload:self.currentURLStirng];
    }
    //通過下載管理器去調用相應的下載操作,通過complete 實現block回調
    [[XZHDownloadImageManager sharedManager] downloadImageWithURLString:URLString complete:^(UIImage *image) {
        self.image = image;
    }];
    //記錄正在下載的url
    self.currentURLStirng = URLString;

}
/**
 *  通過runtime動態添加屬性
 */
- (void)setCurrentURLStirng:(NSString *)currentURLStirng{
    objc_setAssociatedObject(self, XZHCurrentURLString, currentURLStirng, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)currentURLStirng{
   return  objc_getAssociatedObject(self, XZHCurrentURLString);
}
@end

下載管理器

XZHDownloadImageManager.m

@interface XZHDownloadImageManager()
/**
 *  操作緩存
 */
@property (nonatomic,strong)NSMutableDictionary *operationCache;

/**
 *  操作隊列(下載隊列)全局隊列好處:可以控制并發數
 */
@property (nonatomic,strong)NSOperationQueue *queue;
/**
 *  內存緩存,把下載好的圖片保存到內存中
 */
@property (nonatomic,strong)NSMutableDictionary *imageCache;
@end
@implementation XZHDownloadImageManager

+ (instancetype)sharedManager{
    static dispatch_once_t onceToken;
    //只有一個實例
    static XZHDownloadImageManager *manager;
    dispatch_once(&onceToken, ^{
        manager = [[self alloc]init];
    });
    return manager;
}
/**
 *  通過URLString下載并指定回調操作

 */
- (void)downloadImageWithURLString:(NSString *)URLString complete:(void(^)(UIImage *image))complete{
    if ([self.operationCache objectForKey:URLString]) {
        NSLog(@"正在下載");
        return;
    }
    //開始下載前判斷有沒有緩存
    if ([self checkCache:URLString]) {
        UIImage *image = [self.imageCache objectForKey:URLString];
        complete(image);
        return;
    }
    //創建下載操作
    //執行異步下載圖片
    XZHOperation *op = [XZHOperation downloadImageOperationWithURLString:URLString downloadFinished:^(UIImage *image) {
        //下載完成移除操作
        [self.operationCache removeObjectForKey:URLString];
        //回傳圖片
        complete(image);
    }];
    //把操作添加到隊列中
    [self.queue addOperation:op];
    //把操作添加到操作緩存中
    [self.operationCache setObject:op forKey:URLString];
}
/**
 *  取消指定圖片的下載操作
 *
 */
- (void)cancelDownload:(NSString *)URLString{
    //通過url拿到操作
    [self.operationCache[URLString] cancel];
    //從緩存中移除
    [self.operationCache removeObjectForKey:URLString];
}

- (BOOL)checkCache:(NSString *)URLStirng{
    //有內存緩存,直接返回。否則從沙盒取,再保存到內存中,最后還是從內存取
    //先判斷內存
    if ([self.imageCache objectForKey:URLStirng]) {
        NSLog(@"內存緩存");
        return YES;
    }
    //判斷沙盒
    NSString *path = [URLStirng appendCaches];
    UIImage *image = [UIImage imageWithContentsOfFile:path];
    if (image) {
        NSLog(@"沙盒緩存");
        //把圖片保存到內存緩存中
        [self.imageCache setObject:image forKey:URLStirng];
        return YES;
    }
    return NO;
}
#pragma mark -數據懶加載

- (NSOperationQueue *)queue{
    if (_queue==nil) {
        _queue = [[NSOperationQueue alloc]init];
    }
    return _queue;
}


- (NSMutableDictionary *)operationCache{
    if (_operationCache==nil) {
        _operationCache = [NSMutableDictionary dictionary];
    }
    return _operationCache;
}

- (NSMutableDictionary *)imageCache{
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionary];
    }
    return _imageCache;
}
@end

下載操作

XZHOperation.m

@interface XZHOperation()
/**
 *  下載圖片的URL
 */
@property (nonatomic,copy)NSString *URLString;
/**
 *  下載完成后異步回傳下載好的圖片
 */
@property (nonatomic,copy)void (^downloadFinished)(UIImage *image);
@end


@implementation XZHOperation


/**
 *  封裝內部屬性,通過提供的類方法傳入所需的值賦給相應的屬性
 *
 */
+ (instancetype)downloadImageOperationWithURLString:(NSString *)URLStirng downloadFinished:(void(^)(UIImage *image))downloadFinished{
    XZHOperation *download = [[self alloc]init];
    download.URLString = URLStirng;
    download.downloadFinished = downloadFinished;
    return download;
}
/**
 *  通過重寫main方法來干涉操作的內部,從而實現中斷/取消下載
 */
- (void)main{
    //通過包裝一個自動釋放池可以包裝整個操作內存峰值不會太高
    @autoreleasepool {
        //下載圖片
        [NSThread sleepForTimeInterval:1];
        NSURL *url = [NSURL URLWithString:self.URLString];
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //把二進制數據轉換成圖片
        UIImage *image = [UIImage imageWithData:data];
        //把圖片保存到沙盒中
        if (data) {
            [data writeToFile:[self.URLString appendCaches ] atomically:YES];
        }
        //在關鍵點(比較耗時的地方)盤點是否在下載期間已經取消下載了
        //取消下載,直接返回
        if(self.isCancelled){
            NSLog(@"已經取消下載");
            return;
        }
        //回到主線程刷新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          //判斷回調block是否已經被賦值
            if (self.downloadFinished) {
                self.downloadFinished(image);
            }
        }];
    }
}
/**
 *  start方法一直都會被調用,無論是否被取消
 */
//- (void)start{
//    
//}
@end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容