我们通常说的下载就是将数据通过网络接口请求下来,然后将数据保存为本地文件。在这里实际上我们使用到了两种技术,第一种是网络数据请求技术,第二种是数据本地持久化技术。
在iOS开发中进行网络数据请求,iOS7.0之前我们使用NSURLConnection来进行网络请求,但是在iOS7.0之后Apple团队对NSURLConnection进行了重构,并推出了NSURLSession作为替代。相比NSURLConnection,NSURLSession提供了更多的更能,比如:将数据下载到内存中,将数据下载到沙盒中,将数据上传到指定的URL,进行后台下载等功能。而且iOS9.0之后NSURLConnection也被弃用了,所以现在我们在做网络请求时使用NSURLSession就OK了。
数据本地持久化就是将数据保存到沙盒文件中,iOS使用的是沙盒机制,也就是每一个应用都有自己的独立存储空间,iOS的应用程序只能在为该程序创建的文件系统中读取文件。默认情况下,每个沙盒会自动生成三个文件夹:Documents, Library 和 tmp。
- Documents:苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此文件夹。一般在项目中我们会将用户相关的信息放到该文件夹中,比如:用户名密码、用户聊天记录、用户保存的信息等;与用户操作相关的也就是不可再生的内容,会保存到该文件夹中。
- Library:存储程序的默认设置或其它状态信息;
- Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出时删除;在项目中我们会将一些大的图片、音频、视频等文件保存到该文件夹中;像图片、音频、视频等这些可再生的资源一般都放到Caches中。
- tmp:提供一个即时创建临时文件的地方,程序一旦退出就会被清空。
一、小文件的下载
小文件的下载指的是不需要等待很长时间的网络数据请求,可以是数据量比较小的图片或者其他格式的文件。将数据请求完成之后再将数据存成本地文件。
1、使用NSURLConnection进行下载
- 将数据存成本地文件,首先需要考虑将文件存在沙盒的什么位置?
如果是图片的话需要存到Library/Caches中去。
在.m文件中实现一个根据图片URL创建图片路径的方法:
- (NSString *)imageFilePath:(NSString *)imageUrl {
// 1、获取caches文件夹路径
NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 2、创建DownloadImages文件夹
NSString * downloadImagesPath = [cachesPath stringByAppendingPathComponent:@"DownloadImages"];
// 3、创建文件管理器对象
NSFileManager * fileManager = [NSFileManager defaultManager];
// 4、判断文件夹是否存在
if (![fileManager fileExistsAtPath:downloadImagesPath])
{
[fileManager createDirectoryAtPath:downloadImagesPath withIntermediateDirectories:YES attributes:nil error:nil];
}
// 5、拼接图片在沙盒中的路径
/*
因为每一个图片URL对应的是一张图片,而且URL中包含了文件的名称,所以可以用图片的URL来唯一表示图片的名称
因为图像URL中有"/","/"表示的是下级目录的意思,要在存入前替换掉,所以用"_"代替
*/
NSString * imageName = [imageUrl stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
NSString * imageFilePath = [downloadImagesPath stringByAppendingPathComponent:imageName];
// 6、返回文件路径
return imageFilePath;
}
- 根据图片路径加载本地图片,在.m文件中实现加载本地图片的方法:
- (UIImage *)loadLocalImage:(NSString *)imageUrl {
// 1、获取图片路径,根据上一步中创建本地图片路径的方法来获取
NSString * filePath = [self imageFilePath:imageUrl];
// 2、根据本地图片路径创建UIImage对象
UIImage * image = [UIImage imageWithContentsOfFile:filePath];
// 3、判断UIImage对象并返回
if (image != nil) {
return image;
}
return nil;
}
- 创建根据图片URL来请求数据的方法,该方法是需要外部调用的,所以在.h文件中要定义接口,供外部调用;
.h中完整的实现如下:
//声明了一个下载成功的block类型
typedef void (^imageDownLoadSuccess) (NSData *);
//声明一个失败的block类型
typedef void (^imageDownLoadError) (NSError *);
@interface ImageDownLoader : NSObject
@property (nonatomic, copy) imageDownLoadSuccess successBlock;
@property (nonatomic, copy) imageDownLoadError errorBlock;
//声明一个block传值的请求方法
/*
参数解释:
imageUrl:图片的地址;
successBlock: 当请求成功时进行回调;
errorBlock: 当请求失败时进行回调;
*/
- (void)requestImageUrl:(NSString *)imageUrl
successBlock:(imageDownLoadSuccess)successBlock
errorBlock:(imageDownLoadError)errorBlock;
@end
.m中方法的实现如下:
-(void)requestImageUrl:(NSString *)imageUrl
successBlock:(imageDownLoadSuccess)successBlock
errorBlock:(imageDownLoadError)errorBlock {
self.successBlock = successBlock;
self.errorBlock = errorBlock;
// 下载图片之前先检查本地是否已经有图片
UIImage * image = [self loadLocalImage:imageUrl];
NSData *imageData = UIImagePNGRepresentation(image);
//如果图片存在直接跳出;不用下载了
if (imageData) {
self.successBlock(imageData);
return;
}
// 没有本地图片
// 创建URL对象
NSURL *url = [NSURL URLWithString:[imageUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
// 创建request对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 发送异步请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// 如果请求到数据
if (data) {
// 将下载的数据传出去更新UI
self.successBlock(data);
// 下载完成,将图片保存到本地
[data writeToFile:[self imageFilePath:imageUrl] atomically:YES];
}
// 如果有错误信息,将错误信息返回
if (connectionError) {
self.errorBlock(connectionError);
}
}];
}
2、使用NSURLSession进行下载
使用NSURLSession进行数据请求和NSURLConnection的流程基本一致,在请求方法的实现部分做一下修改即可;方法实现的完整代码如下:
-(void)requestImageUrl:(NSString *)imageUrl successBlock:(imageDownLoadSuccess)successBlock errorBlock:(imageDownLoadError)errorBlock {
self.successBlock = successBlock;
self.errorBlock = errorBlock;
// 下载图片之前先检查本地是否已经有图片
UIImage * image = [self loadLocalImage:imageUrl];
NSData *imageData = UIImagePNGRepresentation(image);
//如果图片存在直接跳出;不用下载了
if (imageData) {
self.successBlock(imageData);
return;
}
// 没有本地图片
// 创建URL对象
NSURL *url = [NSURL URLWithString:[imageUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
// 创建request对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 使用URLSession来进行网络请求
// 创建会话配置对象
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 创建会话对象
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
// 创建会话任务对象
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
// 将下载的数据传出去,进行UI更新
self.successBlock(data);
// 下载完成,将图片保存到本地
[data writeToFile:[self imageFilePath:imageUrl] atomically:YES];
}
if (error) {
self.errorBlock(error);
}
}];
// 创建的task都是挂起状态,需要resume才能执行
[task resume];
}
在使用URLSession进行网络请求时,实现步骤一共就两步:创建一个任务,执行任务;
在这两大步中一共使用到了三个类:NSURLSessionConfiguration、NSURLSession和NSURLSessionTask。
下面我们来对这三个类进行简单的解释;
NSURLSessionConfiguration
NSURLSession配置信息,创建配置信息对象时当上传和下载数据时需要做的第一步操作。这些配置信息决定了NSURLSession的种类,HTTP的额外headers,请求的timeout时间,Cookie的接受策略等配置信息。
创建配置信息对象时有三种创建方法:
第一种,默认配置,使用硬盘来存储缓存数据。
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
第二种,临时配置,与默认配置相比,这个配置不会将缓存、cookie等存在本地,只在内存中存在,所以当程序退出时,所有的数据都会消失。
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration
第三种,后台配置,iOS8.0之后可以使用的一种创建配置对象的方法;与默认配置类似,不同的是会在后台开启另一个线程来处理网络数据。当进行后台下载时可以使用该方法来创建配置对象。
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier;
NSURLSession
由Configuration对象来进行配置,然后通过Session对象来创建NSURLSessionTask。
创建NSURLSession对象的方法有三种:
1、不需要自己创建Configuration对象,用于一般的网络数据请求
+ (NSURLSession *)sharedSession;
2、需要自己创建Configuration对象,在上传和下载功能中使用;并且使用该方法时不能使用使用协议方法来监控网络状态
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
3、需要自己创建Configuration对象,在上传和下载功能中使用;在该方法中可以设置代理对象,可以使用协议方法来监控网络状态
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
NSURLSessionTask
NSURLSessionTask是一个抽象类,不能直接使用;使用的是它的子类,一共有4个子类:
NSURLSessionDataTask
数据请求任务
NSURLSessionDownloadTask
文件下载任务
NSURLSessionUploadTask
文件上传任务
NSURLSessionStreamTask
文件流任务
关于NSURLSessionTask的使用我们会在后面的部分中再去详细解释。
附1:
swift版,使用NSURLConnection实现下载的代码:
import UIKit
//请求成功
typealias imageDownLoadSuccess = (NSData) -> ();
//请求失败
typealias imageDownLoadError = (NSError) -> ();
class ImageDownLoader: NSObject {
var successBlock : imageDownLoadSuccess!
var errorBlock : imageDownLoadError!
// 请求图片
func requestImageUrl(imageUrl : NSString, successBlock : imageDownLoadSuccess, errorBlock : imageDownLoadError) {
self.successBlock = successBlock;
self.errorBlock = errorBlock;
// 判断沙盒中是否有缓存图片
let loadImage = self.loadLocalImage(imageUrl) as UIImage?
if((loadImage) != nil) {
let imageData = UIImagePNGRepresentation(loadImage!)
self.successBlock(imageData!)
return;
}
// 创建url对象
let url = NSURL(string: imageUrl.stringByRemovingPercentEncoding!)
// 创建request对象
let request = NSURLRequest(URL: url!);
// 发送异步请求
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response, data, connectionError) -> Void in
if (data != nil) {
// 回调更新UI
self.successBlock(data!)
// data写入沙盒
data?.writeToFile(self.imageFilePath(imageUrl), atomically: true)
}
if (connectionError != nil){
self.errorBlock(connectionError!)
}
}
}
// 本地图片
func loadLocalImage(imageUrl : NSString) -> UIImage?
{
let filePath = self.imageFilePath(imageUrl)
let image = UIImage(contentsOfFile: filePath) as UIImage?
return image
}
// 获取图片沙盒路径
func imageFilePath(imageUrl : NSString) -> String {
let cachesPath: AnyObject? = (NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true) as NSArray).lastObject
let downloadImagesPath : String = cachesPath!.stringByAppendingPathComponent("DownloadImages")
let fileManager = NSFileManager.defaultManager()
if (!fileManager.fileExistsAtPath(downloadImagesPath)) {
do{
try fileManager.createDirectoryAtPath(downloadImagesPath, withIntermediateDirectories: true, attributes: nil) }
catch _{
}
}
let imageName = imageUrl.stringByReplacingOccurrencesOfString("/", withString: "_")
let imageFilePath = (downloadImagesPath as NSString).stringByAppendingPathComponent(imageName as String)
return imageFilePath
}
}
附2:
swift版,使用NSURLSession实现下载的代码:
只将请求的方法进行附录,其他内容与附1中相同;
// 请求图片
func requestImageUrl(imageUrl : NSString, successBlock : imageDownLoadSuccess, errorBlock : imageDownLoadError) {
self.successBlock = successBlock;
self.errorBlock = errorBlock;
// 判断沙盒中是否有缓存图片
let loadImage = self.loadLocalImage(imageUrl) as UIImage?
if((loadImage) != nil) {
let imageData = UIImagePNGRepresentation(loadImage!)
self.successBlock(imageData!)
return;
}
// 创建url对象
let url = NSURL(string: imageUrl.stringByRemovingPercentEncoding!)
// 创建request对象
let request = NSURLRequest(URL: url!);
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration();
let session = NSURLSession(configuration: sessionConfiguration);
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if (data != nil) {
// 回调更新UI
self.successBlock(data!)
// data写入沙盒
data?.writeToFile(self.imageFilePath(imageUrl), atomically: true)
}
if ((error) != nil) {
self.errorBlock(error!);
}
}
task.resume();
}