iOS開發------實現圖片下載快取到本地
在開發過程中很多人都會使用SDWebImage來進行網路圖片的快取,說實話,這個第三方也確實好用,但依照本人的性格,還是一直在想做一版自己的本地快取,比如下載的圖片存到本地,下次再有相同的圖片需要載入,就不用再花冤枉流量來下載了,這次的嘗試雖然相比SDWebImage會差很遠,但是這只是一個開始,畢竟本人的專案經驗有限,但會在以後的學習中不斷的對這個程式碼進行優化,畢竟自己的程式碼可控性要比第三方大得多。
基本的思路就是:第一次載入圖片是要下載的,在下載完成後,存到沙盒目錄一個固定的資料夾(YWebImageFile)下,下次再有相同的url圖片的時候,首先會從這個資料夾中進行查詢,如果存在,從檔案中取出,如果不存在,那就下載,待下載完畢後存到該資料夾下,因為這個類是想用在公司的專案中,用的語言就不再是Swfit
從網上查詢高清大圖,因為這樣子才會觀察得到一個過程,百度就好,大家都懂得,這裡是樓主測試用的圖片地址,下面測試的url是http://c.hiphotos.baidu.com/zhidao/pic/item/730e0cf3d7ca7bcb48f80cb9bc096b63f724a8a1.jpg,測試url的第三個。
static NSString * testImageURL1 = @"http://www.bz55.com/uploads/allimg/150417/139-15041G02614.jpg"; static NSString * testImageURL2 = @"http://a.hiphotos.baidu.com/zhidao/pic/item/faedab64034f78f0b7111ba67b310a55b3191c48.jpg"; static NSString * testImageURL3 = @"http://c.hiphotos.baidu.com/zhidao/pic/item/730e0cf3d7ca7bcb48f80cb9bc096b63f724a8a1.jpg";
因為要看到一個過程,所以最好有一個手動的開始點,所以載入圖片的程式碼就寫在了一個按鈕的點選事件裡面了,這樣最好控嘛
- (IBAction)startLoadImage:(id)sender { //預設初始化label為本地 self.label.text = @"本地圖片!"; NSString * imageURL = self.inputView.text; //可看進度的方法 [self.imageView yw_setImageWithUrl:imageURL withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) { //更改Label NSString * progress = [NSString stringWithFormat:@"%.1f%%",(didFinishTotal * 1.0 / Total) * 100.0]; self.label.text = progress; }]; }
載入效果圖:
不知道為啥,今天的網速格外的好,從列印的效果來看錶示本地已經存在了,不信還可以看看此檔案下已經有了快取檔案,如果加載出現了App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.的列印提示,可以參考之前的一篇部落格App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure 解決
檔案中的快取:
效果圖完畢,來說說原理吧,其實原理前面也說了,就是首先根據url來判斷本地是否已經存在UIImage物件,如果存在,直接取出,如果不存在,那麼就需要下載,然後再將UIImage取出,設定給UIImageView即可,說到檔案的操作,用到的一個系統單例是:NSFileManager,以及網路下載NSURLSession,不得不說樓主是第一次接觸這兩個類,慚愧,但這兩個類的功能也是非常的強,特別是NSURLSession,是用來替代之前的NSURLConnection的,功能很爽,樓主最近也一直在接觸這個類。不說這個了,上程式碼來瞅瞅吧,樓主的習慣就是分成幾個類,所以按照類來說明吧。
YWebFileManager
這個類的功能就是:如果本地有這個圖片檔案,從本地中獲取圖片,是為了在後面的類目中進行呼叫的,外界用的時候不需要呼叫的,在以後的維護中會陸續新增一些新的功能,一下是這個類的宣告檔案:
//
// YWebFileManager.h
// WebImageDemo
//
// Created by YueWen on 16/3/20.
// Copyright © 2016年 YueWen. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
* 負責處理本地檔案(沙盒儲存)的管理者
*/
NS_CLASS_AVAILABLE_IOS(7_0) @interface YWebFileManager : NSObject
/**
* 預設儲存資料夾的大小,單位為MB
*/
//@property (nonatomic, copy, readonly)NSString * fileSize;
/**
* 單例方法
*
* @return YWebFileManager單例物件
*/
+ (instancetype)shareInstanceType NS_AVAILABLE_IOS(7_0);
/**
* 在沙盒中目錄中預設儲存的資料夾中是否存在該檔案
*
* @param url 圖片存在的url
*
* @return true表示存在,false表示不存在
*/
- (BOOL)fileIsExist:(NSString *)url NS_AVAILABLE_IOS(7_0);
/**
* 根據url獲取存在本地的圖片
*
* @param url 下載的url
*
* @return 存在的返回UIImage,不存在返回nil
*/
- (UIImage *)imageWithURL:(NSString *)url NS_AVAILABLE_IOS(7_0);
@end
實現方法:
這裡面有兩個屬性,重寫一下這兩個屬性的getter方法方便取值,一個是獲取系統單例NSFileManager,另一個就是獲取沙盒路徑
#pragma mark - Getter
-(NSFileManager *)fileManager
{
return [NSFileManager defaultManager];
}
-(NSString *)documentPath
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) firstObject];
}
不管是iOS還是其他的操作,在對檔案操作的時候,肯定會檢測檔案存在不存在的問題,對此也實現了兩個方法,一個是檢測是否存在相應的資料夾,一個是檢測是否存在檔案,後者就是用來取出圖片物件的關鍵:
//資料夾是否在沙盒存在
- (BOOL)folderIsExist:(NSString *)folderPath
{
return [self.fileManager fileExistsAtPath:folderPath];
}
//沙盒目錄中預設儲存資料夾中是否存在這個檔案
- (BOOL)fileIsExist:(NSString *)url
{
//拼接路徑
NSString * path = [self.documentPath stringByAppendingFormat:@"/%@/%@",defaultFolderName,url];
return [self.fileManager fileExistsAtPath:path];
}
最後一個便是獲取圖片的方法,UIImageView + YWebImage中就是通過這個介面來獲取響應的圖片物件的
//根據儲存的路徑獲取圖片物件
-(UIImage *)imageWithURL:(NSString *)url
{
//不存在圖片返回nil
if (![self fileIsExist:url])
{
return nil;
}
//拼接路徑
NSString * path = [self.documentPath stringByAppendingFormat:@"/%@/%@",defaultFolderName,url];
//存在圖片返回圖片
return [UIImage imageWithContentsOfFile:path];
}
YWebDownManager
顧名思義,當然這個類就是負責下載圖片的類了,裡面實現了NSURLSession類進行了封裝,當然,這裡面用到的只是NSURLSession功能的九牛一毛,對外介面如下://
// YWebDownManager.h
// WebImageDemo
//
// Created by YueWen on 16/3/20.
// Copyright © 2016年 YueWen. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
* 下載成功後的回撥
*
* @param path 下載成功在本地的路徑
*/
typedef void(^DownManagerFinishBlock)(NSString * path);
/**
* 下載過程中的回撥
*
* @param didFinish 本次下載的檔案大小
* @param didFinishTotal 至此一共下載檔案的大小
* @param Total 一共需要下載檔案的大小
*/
typedef void(^DownManagerProgressBlock)(CGFloat didFinish,CGFloat didFinishTotal,CGFloat Total);
NS_CLASS_AVAILABLE_IOS(7_0) @interface YWebDownManager : NSObject
//開始下載圖片
- (void)startDownImagePath:(NSString *)imagePath NS_AVAILABLE_IOS(7_0);
- (void)startDownImageURL:(NSURL *)imageURL NS_AVAILABLE_IOS(7_0);
//設定相關回調
- (void)downManagerFinishBlockHandle:(DownManagerFinishBlock)downManagerFinishBlockHandle;
- (void)downManagerProgressBlockHandle:(DownManagerProgressBlock)downManagerProgressBlockHandle;
@end
實現方法: 需要了解一下各個屬性的意思,至於NSString什麼的為什麼用copy,不用strong,也歡迎去樓主之前的部落格: iOS開發-------屬性用copy、strong修飾的區別瞭解一下
@interface YWebDownManager ()<NSURLSessionDownloadDelegate>
@property (nonatomic, copy)NSString * imagePath; //記錄圖片url的字串path
@property (nonatomic, copy)NSURL * imageURL; //請求圖片的url
@property (nonatomic, copy)NSString * imageName; //轉型後的圖片名稱
@property (nonatomic, copy)NSString * documentPath; //沙盒路徑
@property (nonatomic, copy)DownManagerFinishBlock finishBlockHandle; //下載完成後的回撥
@property (nonatomic, copy)DownManagerProgressBlock progressBlockHandle; //下載過程中的回撥
@end
設定回撥的方法就是一個簡單賦值的過程,不需要多說了
#pragma mark - 設定回撥的方法
-(void)downManagerFinishBlockHandle:(DownManagerFinishBlock)downManagerFinishBlockHandle
{
self.finishBlockHandle = downManagerFinishBlockHandle;
}
-(void)downManagerProgressBlockHandle:(DownManagerProgressBlock)downManagerProgressBlockHandle
{
self.progressBlockHandle = downManagerProgressBlockHandle;
}
實現兩個getter方法,方便取值,一個依舊是獲得沙盒路徑,另一個就是獲得轉型base64字串的圖片名稱,在本地儲存的圖片名就是這個字串,為什麼需要轉換呢,我們很多時候看到快取的圖片是不是名字特別長呢,是因為害怕圖片名字上會有難識別的特殊字元,所以需要base64來轉換一下。
#pragma mark - Document Path
- (NSString *)documentPath
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) firstObject];
}
#pragma mark - Image Name Base64
-(NSString *)imageName
{
return [YWebDataHandle imageNameForBase64Handle:_imageURL.absoluteString];
}
接著開始用NSURLSession進行下載:這裡會和NSURLConnection相似,因為習慣用AFNetworking,但不得不說這個第三方對於網路請求,著實強大,並且2.0版本也已經對之前的程式碼進行了重構,用NSURLSession代替了之前的NSURLConnection,但是樓主還是推薦看一下原生的類,拓展一下視野。
#pragma mark - 開始下載圖片的方法
-(void)startDownImagePath:(NSString *)imagePath
{
NSLog(@"開始下載圖片啦,路徑為:%@",imagePath);
//賦值
_imagePath = imagePath;
//建立url物件
NSURL * downURL = [[NSURL alloc]initWithString:_imagePath];
//開始根據URL請求圖片
[self startDownImageURL:downURL];
}
- (void)startDownImageURL:(NSURL *)imageURL
{
//開始賦值
_imageURL = imageURL;
//建立請求物件
NSURLRequest * request = [NSURLRequest requestWithURL:_imageURL];
//建立網路請求物件
NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
//獲取下載物件
NSURLSessionDownloadTask * downLoadTask = [session downloadTaskWithRequest:request];
//開始請求
[downLoadTask resume];
}
實現代理方法,主要就是往本地存以及往外傳值得一個過程:
#pragma mark - NSURLSessionDownload Delegate
//下載完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
//路徑字串
NSString * path = [NSString stringWithFormat:@"%@/YWebImageFile/%@",self.documentPath,self.imageName];
//獲取建立下載到的路徑url
NSURL * url = [NSURL fileURLWithPath:path];
//獲取檔案管理者
NSFileManager * fileManager = [NSFileManager defaultManager];
//存到檔案
[fileManager moveItemAtURL:location toURL:url error:nil];
//主執行緒回撥
dispatch_async(dispatch_get_main_queue(), ^{
//執行回撥,傳出路徑
if (self.finishBlockHandle) {
self.finishBlockHandle(path);
}
});
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//主執行緒進行回撥
dispatch_async(dispatch_get_main_queue(), ^{
//執行過程的回撥
if (self.progressBlockHandle) {
self.progressBlockHandle(bytesWritten,totalBytesWritten,totalBytesExpectedToWrite);
}
});
}
YWebDataHandle
這個類就是處理字串,將其轉成base64字元,只有這麼一個方法:NS_AVAILABLE_IOS(7_0) @interface YWebDataHandle : NSObject
/**
* 將路徑或者url轉成base64處理的字串
*
* @param path 需要處理的字串
*
* @return 處理完畢的字串
*/
+ (NSString *)imageNameForBase64Handle:(NSString *)path;
@end
@implementation YWebDataHandle
+(NSString *)imageNameForBase64Handle:(NSString *)path
{
NSData * data = [path dataUsingEncoding:NSUTF8StringEncoding];
NSString * imageNameBase = [data base64EncodedStringWithOptions:0];
return [imageNameBase substringToIndex:imageNameBase.length - 2];
}
@end
UIImageView + YWebImage
實現UIImageView的類目,完成對圖片的下載或者呼叫,是主要的對外介面#import <UIKit/UIKit.h>
/**
* 下載過程中的回撥
*
* @param didFinish 本次下載的檔案大小
* @param didFinishTotal 至此一共下載檔案的大小
* @param Total 一共需要下載檔案的大小
*/
typedef void(^DownManagerProgressBlock)(CGFloat didFinish,CGFloat didFinishTotal,CGFloat Total);
@interface UIImageView (YWebImage)
//根據url設定圖片
- (void)yw_setImageWithUrl:(NSString *)url;
- (void)yw_setImageWithUrl:(NSString *)url
withProgressHandle:(DownManagerProgressBlock)progresshandle;
//根據url設定圖片,並支援預設佔位圖
- (void)yw_setImageWithUrl:(NSString *)url
placeHolderImage:(UIImage *)placeHodlerImage;
- (void)yw_setImageWithUrl:(NSString *)url
placeHolderImage:(UIImage *)placeHodlerImage
withProgressHandle:(DownManagerProgressBlock)progresshandle;
@end
實現方法完成四個即可,看似是四個,有經驗的人一看也就知道其實只實現一個即可,其他的呼叫主方法即可:
-(void)yw_setImageWithUrl:(NSString *)url
withProgressHandle:(DownManagerProgressBlock)progresshandle
{
//處理url
NSString * urlHandle = [YWebDataHandle imageNameForBase64Handle:url];
//本地查詢
if([[YWebFileManager shareInstanceType] fileIsExist:urlHandle])//如果本地存在返回圖片
{
NSLog(@"本地已經存在這個圖片了!");
self.image = [[YWebFileManager shareInstanceType] imageWithURL:urlHandle];
}
//不存在需要根據url下載
else
{
NSLog(@"本地沒有這個圖片!");
//開始下載
[self downImage:url withProgressHandle:progresshandle];
}
}
其它的三呼叫此方法即可:
{
[self yw_setImageWithUrl:url withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {}];
}
- (void)yw_setImageWithUrl:(NSString *)url placeHolderImage:(UIImage *)placeHodlerImage
{
[self yw_setImageWithUrl:url placeHolderImage:placeHodlerImage withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {}];
}
- (void)yw_setImageWithUrl:(NSString *)url placeHolderImage:(UIImage *)placeHodlerImage withProgressHandle:(DownManagerProgressBlock)progresshandle
{
//設定佔位圖
self.image = placeHodlerImage;
//開始設定圖片
[self yw_setImageWithUrl:url withProgressHandle:progresshandle];
}
因為下載過程程式碼比較多,依照樓主的習慣還是拿出來當成一個方法:
//開始下載圖片
- (void)downImage:(NSString *)url
{
[self downImage:url withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {}];
}
//開始下載圖片
- (void)downImage:(NSString *)url
withProgressHandle:(DownManagerProgressBlock)progresshandle
{
YWebDownManager * webDownManager = [[YWebDownManager alloc]init];
//開始下載
[webDownManager startDownImagePath:url];
//設定下載完畢的回撥
[webDownManager downManagerFinishBlockHandle:^(NSString *path) {
//獲得當前的圖片物件
UIImage * image = [UIImage imageWithContentsOfFile:path];
self.image = image;
}];
//設定下載過程的回撥
[webDownManager downManagerProgressBlockHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {
//進行回撥
progresshandle(didFinish,didFinishTotal,Total);
}];
}
這樣基本的功能就已經實現了,以後會陸續的增加這個類的功能,[[Thanks alloc]init](對Objective-C致敬,也感謝您的觀看)