1. 程式人生 > >iOS開發------實現圖片下載快取到本地

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致敬,也感謝您的觀看)