我們通常說的下載就是將數(shù)據(jù)通過網(wǎng)絡(luò)接口請求下來,然后將數(shù)據(jù)保存為本地文件。在這里實際上我們使用到了兩種技術(shù),第一種是網(wǎng)絡(luò)數(shù)據(jù)請求技術(shù),第二種是數(shù)據(jù)本地持久化技術(shù)。
在iOS開發(fā)中進行網(wǎng)絡(luò)數(shù)據(jù)請求,iOS7.0之前我們使用NSURLConnection來進行網(wǎng)絡(luò)請求,但是在iOS7.0之后Apple團隊對NSURLConnection進行了重構(gòu),并推出了NSURLSession作為替代。相比NSURLConnection,NSURLSession提供了更多的更能,比如:將數(shù)據(jù)下載到內(nèi)存中,將數(shù)據(jù)下載到沙盒中,將數(shù)據(jù)上傳到指定的URL,進行后臺下載等功能。而且iOS9.0之后NSURLConnection也被棄用了,所以現(xiàn)在我們在做網(wǎng)絡(luò)請求時使用NSURLSession就OK了。
數(shù)據(jù)本地持久化就是將數(shù)據(jù)保存到沙盒文件中,iOS使用的是沙盒機制,也就是每一個應(yīng)用都有自己的獨立存儲空間,iOS的應(yīng)用程序只能在為該程序創(chuàng)建的文件系統(tǒng)中讀取文件。默認情況下,每個沙盒會自動生成三個文件夾:Documents, Library 和 tmp。
- Documents:蘋果建議將程序中建立的或在程序中瀏覽到的文件數(shù)據(jù)保存在該目錄下,iTunes備份和恢復(fù)的時候會包括此文件夾。一般在項目中我們會將用戶相關(guān)的信息放到該文件夾中,比如:用戶名密碼、用戶聊天記錄、用戶保存的信息等;與用戶操作相關(guān)的也就是不可再生的內(nèi)容,會保存到該文件夾中。
- Library:存儲程序的默認設(shè)置或其它狀態(tài)信息;
- Library/Caches:存放緩存文件,iTunes不會備份此目錄,此目錄下文件不會在應(yīng)用退出時刪除;在項目中我們會將一些大的圖片、音頻、視頻等文件保存到該文件夾中;像圖片、音頻、視頻等這些可再生的資源一般都放到Caches中。
- tmp:提供一個即時創(chuàng)建臨時文件的地方,程序一旦退出就會被清空。
一、小文件的下載
小文件的下載指的是不需要等待很長時間的網(wǎng)絡(luò)數(shù)據(jù)請求,可以是數(shù)據(jù)量比較小的圖片或者其他格式的文件。將數(shù)據(jù)請求完成之后再將數(shù)據(jù)存成本地文件。
1、使用NSURLConnection進行下載
- 將數(shù)據(jù)存成本地文件,首先需要考慮將文件存在沙盒的什么位置?
如果是圖片的話需要存到Library/Caches中去。
在.m文件中實現(xiàn)一個根據(jù)圖片URL創(chuàng)建圖片路徑的方法:

- (NSString *)imageFilePath:(NSString *)imageUrl {
// 1、獲取caches文件夾路徑
NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 2、創(chuàng)建DownloadImages文件夾
NSString * downloadImagesPath = [cachesPath stringByAppendingPathComponent:@"DownloadImages"];
// 3、創(chuàng)建文件管理器對象
NSFileManager * fileManager = [NSFileManager defaultManager];
// 4、判斷文件夾是否存在
if (![fileManager fileExistsAtPath:downloadImagesPath])
{
[fileManager createDirectoryAtPath:downloadImagesPath withIntermediateDirectories:YES attributes:nil error:nil];
}
// 5、拼接圖片在沙盒中的路徑
/*
因為每一個圖片URL對應(yīng)的是一張圖片,而且URL中包含了文件的名稱,所以可以用圖片的URL來唯一表示圖片的名稱
因為圖像URL中有"/","/"表示的是下級目錄的意思,要在存入前替換掉,所以用"_"代替
*/
NSString * imageName = [imageUrl stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
NSString * imageFilePath = [downloadImagesPath stringByAppendingPathComponent:imageName];
// 6、返回文件路徑
return imageFilePath;
}
- 根據(jù)圖片路徑加載本地圖片,在.m文件中實現(xiàn)加載本地圖片的方法:
- (UIImage *)loadLocalImage:(NSString *)imageUrl {
// 1、獲取圖片路徑,根據(jù)上一步中創(chuàng)建本地圖片路徑的方法來獲取
NSString * filePath = [self imageFilePath:imageUrl];
// 2、根據(jù)本地圖片路徑創(chuàng)建UIImage對象
UIImage * image = [UIImage imageWithContentsOfFile:filePath];
// 3、判斷UIImage對象并返回
if (image != nil) {
return image;
}
return nil;
}
- 創(chuàng)建根據(jù)圖片URL來請求數(shù)據(jù)的方法,該方法是需要外部調(diào)用的,所以在.h文件中要定義接口,供外部調(diào)用;
.h中完整的實現(xiàn)如下:
//聲明了一個下載成功的block類型
typedef void (^imageDownLoadSuccess) (NSData *);
//聲明一個失敗的block類型
typedef void (^imageDownLoadError) (NSError *);
@interface ImageDownLoader : NSObject
@property (nonatomic, copy) imageDownLoadSuccess successBlock;
@property (nonatomic, copy) imageDownLoadError errorBlock;
//聲明一個block傳值的請求方法
/*
參數(shù)解釋:
imageUrl:圖片的地址;
successBlock: 當請求成功時進行回調(diào);
errorBlock: 當請求失敗時進行回調(diào);
*/
- (void)requestImageUrl:(NSString *)imageUrl
successBlock:(imageDownLoadSuccess)successBlock
errorBlock:(imageDownLoadError)errorBlock;
@end
.m中方法的實現(xiàn)如下:
-(void)requestImageUrl:(NSString *)imageUrl
successBlock:(imageDownLoadSuccess)successBlock
errorBlock:(imageDownLoadError)errorBlock {
self.successBlock = successBlock;
self.errorBlock = errorBlock;
// 下載圖片之前先檢查本地是否已經(jīng)有圖片
UIImage * image = [self loadLocalImage:imageUrl];
NSData *imageData = UIImagePNGRepresentation(image);
//如果圖片存在直接跳出;不用下載了
if (imageData) {
self.successBlock(imageData);
return;
}
// 沒有本地圖片
// 創(chuàng)建URL對象
NSURL *url = [NSURL URLWithString:[imageUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
// 創(chuàng)建request對象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 發(fā)送異步請求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// 如果請求到數(shù)據(jù)
if (data) {
// 將下載的數(shù)據(jù)傳出去更新UI
self.successBlock(data);
// 下載完成,將圖片保存到本地
[data writeToFile:[self imageFilePath:imageUrl] atomically:YES];
}
// 如果有錯誤信息,將錯誤信息返回
if (connectionError) {
self.errorBlock(connectionError);
}
}];
}
2、使用NSURLSession進行下載
使用NSURLSession進行數(shù)據(jù)請求和NSURLConnection的流程基本一致,在請求方法的實現(xiàn)部分做一下修改即可;方法實現(xiàn)的完整代碼如下:
-(void)requestImageUrl:(NSString *)imageUrl successBlock:(imageDownLoadSuccess)successBlock errorBlock:(imageDownLoadError)errorBlock {
self.successBlock = successBlock;
self.errorBlock = errorBlock;
// 下載圖片之前先檢查本地是否已經(jīng)有圖片
UIImage * image = [self loadLocalImage:imageUrl];
NSData *imageData = UIImagePNGRepresentation(image);
//如果圖片存在直接跳出;不用下載了
if (imageData) {
self.successBlock(imageData);
return;
}
// 沒有本地圖片
// 創(chuàng)建URL對象
NSURL *url = [NSURL URLWithString:[imageUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
// 創(chuàng)建request對象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 使用URLSession來進行網(wǎng)絡(luò)請求
// 創(chuàng)建會話配置對象
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 創(chuàng)建會話對象
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
// 創(chuàng)建會話任務(wù)對象
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
// 將下載的數(shù)據(jù)傳出去,進行UI更新
self.successBlock(data);
// 下載完成,將圖片保存到本地
[data writeToFile:[self imageFilePath:imageUrl] atomically:YES];
}
if (error) {
self.errorBlock(error);
}
}];
// 創(chuàng)建的task都是掛起狀態(tài),需要resume才能執(zhí)行
[task resume];
}
在使用URLSession進行網(wǎng)絡(luò)請求時,實現(xiàn)步驟一共就兩步:創(chuàng)建一個任務(wù),執(zhí)行任務(wù);
在這兩大步中一共使用到了三個類:NSURLSessionConfiguration、NSURLSession和NSURLSessionTask。
下面我們來對這三個類進行簡單的解釋;
NSURLSessionConfiguration
NSURLSession配置信息,創(chuàng)建配置信息對象時當上傳和下載數(shù)據(jù)時需要做的第一步操作。這些配置信息決定了NSURLSession的種類,HTTP的額外headers,請求的timeout時間,Cookie的接受策略等配置信息。
創(chuàng)建配置信息對象時有三種創(chuàng)建方法:
第一種,默認配置,使用硬盤來存儲緩存數(shù)據(jù)。
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
第二種,臨時配置,與默認配置相比,這個配置不會將緩存、cookie等存在本地,只在內(nèi)存中存在,所以當程序退出時,所有的數(shù)據(jù)都會消失。
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration
第三種,后臺配置,iOS8.0之后可以使用的一種創(chuàng)建配置對象的方法;與默認配置類似,不同的是會在后臺開啟另一個線程來處理網(wǎng)絡(luò)數(shù)據(jù)。當進行后臺下載時可以使用該方法來創(chuàng)建配置對象。
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier;
NSURLSession
由Configuration對象來進行配置,然后通過Session對象來創(chuàng)建NSURLSessionTask。
創(chuàng)建NSURLSession對象的方法有三種:
1、不需要自己創(chuàng)建Configuration對象,用于一般的網(wǎng)絡(luò)數(shù)據(jù)請求
+ (NSURLSession *)sharedSession;
2、需要自己創(chuàng)建Configuration對象,在上傳和下載功能中使用;并且使用該方法時不能使用使用協(xié)議方法來監(jiān)控網(wǎng)絡(luò)狀態(tài)
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
3、需要自己創(chuàng)建Configuration對象,在上傳和下載功能中使用;在該方法中可以設(shè)置代理對象,可以使用協(xié)議方法來監(jiān)控網(wǎng)絡(luò)狀態(tài)
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
NSURLSessionTask
NSURLSessionTask是一個抽象類,不能直接使用;使用的是它的子類,一共有4個子類:
NSURLSessionDataTask 數(shù)據(jù)請求任務(wù)
NSURLSessionDownloadTask 文件下載任務(wù)
NSURLSessionUploadTask 文件上傳任務(wù)
NSURLSessionStreamTask 文件流任務(wù)
關(guān)于NSURLSessionTask的使用我們會在后面的部分中再去詳細解釋。
附1:
swift版,使用NSURLConnection實現(xiàn)下載的代碼:
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;
}
// 創(chuàng)建url對象
let url = NSURL(string: imageUrl.stringByRemovingPercentEncoding!)
// 創(chuàng)建request對象
let request = NSURLRequest(URL: url!);
// 發(fā)送異步請求
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response, data, connectionError) -> Void in
if (data != nil) {
// 回調(diào)更新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實現(xiàn)下載的代碼:
只將請求的方法進行附錄,其他內(nèi)容與附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;
}
// 創(chuàng)建url對象
let url = NSURL(string: imageUrl.stringByRemovingPercentEncoding!)
// 創(chuàng)建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) {
// 回調(diào)更新UI
self.successBlock(data!)
// data寫入沙盒
data?.writeToFile(self.imageFilePath(imageUrl), atomically: true)
}
if ((error) != nil) {
self.errorBlock(error!);
}
}
task.resume();
}