ios 图片下载并保存到本地

12 篇文章 0 订阅

 在开发过程中很多人都会使用SDWebImage来进行网络图片的缓存,说实话,这个第三方也确实好用,但依照本人的性格,还是一直在想做一版自己的本地缓存,比如下载的图片存到本地,下次再有相同的图片需要加载,就不用再花冤枉流量来下载了,这次的尝试虽然相比SDWebImage会差很远,但是这只是一个开始,毕竟本人的项目经验有限,但会在以后的学习中不断的对这个代码进行优化,毕竟自己的代码可控性要比第三方大得多。


最新代码的GitHub:https://github.com/YRunIntoLove/YWebImage


      基本的思路就是:第一次加载图片是要下载的,在下载完成后,存到沙盒目录一个固定的文件夹(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致敬,也感谢您的观看)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值