使用NSURLConnection實現(xiàn)下載
1. 小文件下載
- 第一種方式(NSData)
//使用NSDta直接加載網(wǎng)絡(luò)上的url資源(不考慮線程)
-(void)dataDownload {
//1.確定資源路徑
NSURL *url = [NSURL URLWithString:@"http://tupian.qqjay.com/u/2013/1127/19_222949_14.jpg"];
//2.根據(jù)URL加載對應(yīng)的資源
NSData *data = [NSData dataWithContentsOfURL:url];
//3.轉(zhuǎn)換并顯示數(shù)據(jù)
UIImage *image = [UIImage imageWithData:data];
self.imageView.image = image;
}
- 第二種方式(NSURLConnection-sendAsync)
//使用NSURLConnection發(fā)送異步請求下載文件資源
-(void)connectDownload {
//1.確定請求路徑
NSURL *url = [NSURL URLWithString:@"http://tupian.qqjay.com/u/2013/1127/19_222949_14.jpg"];
//2.創(chuàng)建請求對象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.使用NSURLConnection發(fā)送一個異步請求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
//4.拿到并處理數(shù)據(jù)
UIImage *image = [UIImage imageWithData:data];
self.imageView.image = image;
}];
}
- 第三種方式(NSURLConnection-delegate)
//使用NSURLConnection設(shè)置代理發(fā)送異步請求的方式下載文件
-(void)connectionDelegateDownload {
//1.確定請求路徑
NSURL *url = [NSURL URLWithString:@"下載文件的URL地址"];
//2.創(chuàng)建請求對象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.使用NSURLConnection設(shè)置代理并發(fā)送異步請求
[NSURLConnection connectionWithRequest:request delegate:self];
}
pragma mark--NSURLConnectionDataDelegate
//當(dāng)接收到服務(wù)器響應(yīng)的時候調(diào)用搪缨,該方法只會調(diào)用一次
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//創(chuàng)建一個容器铸史,用來接收服務(wù)器返回的數(shù)據(jù)
self.fileData = [NSMutableData data];
//獲得當(dāng)前要下載文件的總大小(通過響應(yīng)頭得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
self.totalLength = res.expectedContentLength;
NSLog(@"%zd",self.totalLength);
//拿到服務(wù)器端推薦的文件名稱
self.fileName = res.suggestedFilename;
}
//當(dāng)接收到服務(wù)器返回的數(shù)據(jù)時會調(diào)用
//該方法可能會被調(diào)用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// NSLog(@"%s",__func__);
//拼接每次下載的數(shù)據(jù)
[self.fileData appendData:data];
//計算當(dāng)前下載進(jìn)度并刷新UI顯示
self.currentLength = self.fileData.length;
NSLog(@"%f",1.0* self.currentLength/self.totalLength);
self.progressView.progress = 1.0* self.currentLength/self.totalLength;
}
//當(dāng)網(wǎng)絡(luò)請求結(jié)束之后調(diào)用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//文件下載完畢把接受到的文件數(shù)據(jù)寫入到沙盒中保存
//1.確定要保存文件的全路徑
//caches文件夾路徑
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fullPath = [caches stringByAppendingPathComponent:self.fileName];
//2.寫數(shù)據(jù)到文件中
[self.fileData writeToFile:fullPath atomically:YES];
NSLog(@"%@",fullPath);
}
//當(dāng)請求失敗的時候調(diào)用該方法
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"%s",__func__);
}
2. 大文件下載
- 實現(xiàn)思路
邊接收數(shù)據(jù)邊寫文件以解決內(nèi)存越來越大的問題 - 核心代碼
//當(dāng)接收到服務(wù)器響應(yīng)的時候調(diào)用,該方法只會調(diào)用一次
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//0.獲得當(dāng)前要下載文件的總大小(通過響應(yīng)頭得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
self.totalLength = res.expectedContentLength;
NSLog(@"%zd",self.totalLength);
//創(chuàng)建一個新的文件,用來當(dāng)接收到服務(wù)器返回數(shù)據(jù)的時候往該文件中寫入數(shù)據(jù) //1.獲取文件管理者
NSFileManager *manager = [NSFileManager defaultManager];
//2.拼接文件的全路徑
//caches文件夾路徑
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fullPath = [caches stringByAppendingPathComponent:res.suggestedFilename];
self.fullPath = fullPath;
//3.創(chuàng)建一個空的文件
[manager createFileAtPath:fullPath contents:nil attributes:nil];
}
//當(dāng)接收到服務(wù)器返回的數(shù)據(jù)時會調(diào)用
//該方法可能會被調(diào)用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//1.創(chuàng)建一個用來向文件中寫數(shù)據(jù)的文件句柄
//注意當(dāng)下載完成之后倔喂,該文件句柄需要關(guān)閉,調(diào)用closeFile方法
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath];
//2.設(shè)置寫數(shù)據(jù)的位置(追加)
[handle seekToEndOfFile];
//3.寫數(shù)據(jù) [handle writeData:data];
//4.計算當(dāng)前文件的下載進(jìn)度
self.currentLength += data.length;
NSLog(@"%f",1.0* self.currentLength/self.totalLength);
self.progressView.progress = 1.0* self.currentLength/self.totalLength;
}
3. 大文件斷點(diǎn)續(xù)傳
- 實現(xiàn)思路
在下載文件的時候不再是整塊的從頭開始下載靖苇,而是看當(dāng)前文件已經(jīng)下載到哪個地方席噩,然后從該地方接著往后面下載∠捅冢可以通過在請求對象中設(shè)置請求頭實現(xiàn)悼枢。 - 解決方案(設(shè)置請求頭)
//2.創(chuàng)建請求對象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//2.1 設(shè)置下載文件的某一部分
// 只要設(shè)置HTTP請求頭的Range屬性, 就可以實現(xiàn)從指定位置開始下載
/*
表示頭500個字節(jié):Range: bytes=0-499
表示第二個500字節(jié):Range: bytes=500-999
表示最后500個字節(jié):Range: bytes=-500
表示500字節(jié)以后的范圍:Range: bytes=500-
*/
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];
- 注意點(diǎn)(下載進(jìn)度并判斷是否需要重新創(chuàng)建文件)
//獲得當(dāng)前要下載文件的總大小(通過響應(yīng)頭得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
//注意點(diǎn):res.expectedContentLength獲得是本次請求要下載的文件的大衅⒉稹(并非是完整的文件的大新鳌)
//因此:文件的總大小 == 本次要下載的文件大小+已經(jīng)下載的文件的大小
self.totalLength = res.expectedContentLength + self.currentLength;
NSLog(@"----------------------------%zd",self.totalLength);
//0 判斷當(dāng)前是否已經(jīng)下載過,如果當(dāng)前文件已經(jīng)存在名船,那么直接返回
if (self.currentLength >0) { return; }
輸出流
使用輸出流也可以實現(xiàn)和NSFileHandle相同的功能
如何使用
//1.創(chuàng)建一個數(shù)據(jù)輸出流
/*
第一個參數(shù):二進(jìn)制的流數(shù)據(jù)要寫入到哪里
第二個參數(shù):采用什么樣的方式寫入流數(shù)據(jù)绰上,如果YES則表示追加,如果是NO則表示覆蓋
*/
NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:fullPath append:YES];
//只要調(diào)用了該方法就會往文件中寫數(shù)據(jù)
//如果文件不存在渠驼,那么會自動的創(chuàng)建一個
[stream open];
self.stream = stream;
//2.當(dāng)接收到數(shù)據(jù)的時候?qū)憯?shù)據(jù)
//使用輸出流寫數(shù)據(jù)
/*
第一個參數(shù):要寫入的二進(jìn)制數(shù)據(jù)
第二個參數(shù):要寫入的數(shù)據(jù)的大小
*/
[self.stream write:data.bytes maxLength:data.length];
//3.當(dāng)文件下載完畢的時候關(guān)閉輸出流
//關(guān)閉輸出流
[self.stream close];
self.stream = nil;
- 使用多線程下載文件的思路
- 01 開啟多條線程蜈块,每條線程都只下載文件的一部分(通過設(shè)置請求頭中的Range來實現(xiàn))
- 02 創(chuàng)建一個和需要下載文件大小一致的文件,判斷當(dāng)前是那個線程迷扇,根據(jù)當(dāng)前的線程來判斷下載的數(shù)據(jù)應(yīng)該寫入到文件中的哪個位置百揭。(假設(shè)開5條線程來下載10M的文件,那么線程1下載0-2M蜓席,線程2下載2-4M一次類推器一,當(dāng)接收到服務(wù)器返回的數(shù)據(jù)之后應(yīng)該先判斷當(dāng)前線程是哪個線程,假如當(dāng)前線程是線程2厨内,那么在寫數(shù)據(jù)的時候就從文件的2M位置開始寫入)
- 03 代碼相關(guān):使用NSFileHandle這個類的seekToFileOfSet方法祈秕,來向文件中特定的位置寫入數(shù)據(jù)渺贤。
- 04 技術(shù)相關(guān)
a.每個線程通過設(shè)置請求頭下載文件中的某一個部分
b.通過NSFileHandle向文件中的指定位置寫數(shù)據(jù)
使用NSURLSession實現(xiàn)下載
1. NSURLSession下載文件-代理
- 創(chuàng)建NSURLSession對象,設(shè)置代理(默認(rèn)配置)
//1.創(chuàng)建NSURLSession,并設(shè)置代理
/*
第一個參數(shù):session對象的全局配置設(shè)置踢步,一般使用默認(rèn)配置就可以
第二個參數(shù):誰成為session對象的代理
第三個參數(shù):代理方法在哪個隊列中執(zhí)行(在哪個線程中調(diào)用),如果是主隊列那么在主線程中執(zhí)行癣亚,如果是非主隊列,那么在子線程中執(zhí)行
*/
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
- 根據(jù)Session對象創(chuàng)建一個NSURLSessionDataTask任務(wù)(post和get選擇)
//創(chuàng)建task
NSURL *url = [NSURL URLWithString:@"http://tupian.qqjay.com/u/2013/1127/19_222949_14.jpg"];
//注意:如果要發(fā)送POST請求获印,那么請使用dataTaskWithRequest,設(shè)置一些請求頭信息
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url];
- 執(zhí)行任務(wù)(其它方法述雾,如暫停、取消等)
//啟動task
//[dataTask resume];
//其它方法兼丰,如取消任務(wù)玻孟,暫停任務(wù)等
//[dataTask cancel];
//[dataTask suspend];
```
4. 遵守代理協(xié)議,實現(xiàn)代理方法(3個相關(guān)的代理方法)
```
/*
1.當(dāng)接收到服務(wù)器響應(yīng)的時候調(diào)用
session:發(fā)送請求的session對象
dataTask:根據(jù)NSURLSession創(chuàng)建的task任務(wù)
response:服務(wù)器響應(yīng)信息(響應(yīng)頭)
completionHandler:通過該block回調(diào)鳍征,告訴服務(wù)器端是否接收返回的數(shù)據(jù)
*/
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler
/*
2.當(dāng)接收到服務(wù)器返回的數(shù)據(jù)時調(diào)用 該方法可能會被調(diào)用多次
*/
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data
/*
3.當(dāng)請求完成之后調(diào)用該方法 不論是請求成功還是請求失敗都調(diào)用該方法黍翎,如果請求失敗,那么error對象有值艳丛,否則那么error對象為空
*/
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
```
5. 當(dāng)接收到服務(wù)器響應(yīng)的時候匣掸,告訴服務(wù)器接收數(shù)據(jù)(調(diào)用block)
```
//默認(rèn)情況下,當(dāng)接收到服務(wù)器響應(yīng)之后氮双,服務(wù)器認(rèn)為客戶端不需要接收數(shù)據(jù)碰酝,所以后面的代理方法不會調(diào)用
//如果需要繼續(xù)接收服務(wù)器返回的數(shù)據(jù),那么需要調(diào)用block,并傳入對應(yīng)的策略
/*
NSURLSessionResponseCancel = 0, 取消任務(wù)
NSURLSessionResponseAllow = 1, 接收任務(wù)
NSURLSessionResponseBecomeDownload = 2, 轉(zhuǎn)變成下載
NSURLSessionResponseBecomeStream NS_ENUM_AVAILABLE(10_11, 9_0) = 3, 轉(zhuǎn)變成流
*/
completionHandler(NSURLSessionResponseAllow);
```
###2. NSURLSessionDownloadTask實現(xiàn)大文件下載
1. 使用NSURLSession和NSURLSessionDownload可以很方便的實現(xiàn)文件下載操作
```
/*
第一個參數(shù):要下載文件的url路徑
第二個參數(shù):當(dāng)接收完服務(wù)器返回的數(shù)據(jù)之后調(diào)用該block
location:下載的文件的保存地址(默認(rèn)是存儲在沙盒中tmp文件夾下面戴差,隨時會被刪除)
response:服務(wù)器響應(yīng)信息送爸,響應(yīng)頭
error:該請求的錯誤信息
*/
//說明:downloadTaskWithURL方法已經(jīng)實現(xiàn)了在下載文件數(shù)據(jù)的過程中邊下載文件數(shù)據(jù),邊寫入到沙盒文件的操作
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * __nullable location, NSURLResponse * __nullable response, NSError * __nullable error)
```
2. downloadTaskWithURL內(nèi)部默認(rèn)已經(jīng)實現(xiàn)了變下載邊寫入操作暖释,所以不用開發(fā)人員擔(dān)心內(nèi)存問題
3. 文件下載后默認(rèn)保存在tmp文件目錄袭厂,需要開發(fā)人員手動的剪切到合適的沙盒目錄
4. 缺點(diǎn):沒有辦法監(jiān)控下載進(jìn)度
###3. 使用NSURLSessionDownloadTask實現(xiàn)大文件下載-監(jiān)聽下載進(jìn)度
1. 創(chuàng)建NSURLSession并設(shè)置代理,通過NSURLSessionDownloadTask并以代理的方式來完成大文件的下載
```
//1.創(chuàng)建NSULRSession,設(shè)置代理
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//2.創(chuàng)建task
NSURL *url = [NSURL URLWithString:@"URL字符串"];
self.downloadTask = [self.session downloadTaskWithURL:url];
//3.執(zhí)行task
[self.downloadTask resume];
```
2. 常用代理方法的說明
```
/*
1.當(dāng)接收到下載數(shù)據(jù)的時候調(diào)用,可以在該方法中監(jiān)聽文件下載的進(jìn)度
該方法會被調(diào)用多次
totalBytesWritten:已經(jīng)寫入到文件中的數(shù)據(jù)大小
totalBytesExpectedToWrite:目前文件的總大小
bytesWritten:本次下載的文件數(shù)據(jù)大小
*/
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
/*
2.恢復(fù)下載的時候調(diào)用該方法
fileOffset:恢復(fù)之后球匕,要從文件的什么地方開發(fā)下載
expectedTotalBytes:該文件數(shù)據(jù)的總大小
*/
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
/*
3.下載完成之后調(diào)用該方法
*/
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location
/*
4.請求完成之后調(diào)用
如果請求失敗纹磺,那么error有值
*/
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
```
3. 實現(xiàn)斷點(diǎn)下載相關(guān)代碼
```
//如果任務(wù),取消了那么以后就不能恢復(fù)了
// [self.downloadTask cancel];
//如果采取這種方式來取消任務(wù)亮曹,那么該方法會通過resumeData保存當(dāng)前文件的下載信息
//只要有了這份信息爽航,以后就可以通過這些信息來恢復(fù)下載
[self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) { self.resumeData = resumeData;}];
//繼續(xù)下載
//首先通過之前保存的resumeData信息,創(chuàng)建一個下載任務(wù)
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume];
```
4. 計算當(dāng)前下載進(jìn)度
```
//獲取文件下載進(jìn)度
self.progress.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;
```
5. 局限性
- 如果用戶點(diǎn)擊暫停之后退出程序乾忱,那么需要把恢復(fù)下載的數(shù)據(jù)寫一份到沙盒,代碼復(fù)雜度更高
- 如果用戶在下載中途未保存恢復(fù)下載數(shù)據(jù)即退出程序历极,則不具備可操作性
###4. 使用NSURLSessionDataTask實現(xiàn)大文件離線斷點(diǎn)下載(完整)
1. 關(guān)于NSOutputStream的使用
```
//1. 創(chuàng)建一個輸入流,數(shù)據(jù)追加到文件的屁股上
//把數(shù)據(jù)寫入到指定的文件地址窄瘟,如果當(dāng)前文件不存在,則會自動創(chuàng)建
NSOutputStream *stream = [[NSOutputStream alloc]initWithURL:[NSURL fileURLWithPath:[self fullPath]] append:YES];
//2. 打開流
[stream open];
//3. 寫入流數(shù)據(jù)
[stream write:data.bytes maxLength:data.length];
//4.當(dāng)不需要的時候應(yīng)該關(guān)閉流
[stream close];
```
2. 關(guān)于網(wǎng)絡(luò)請求請求頭的設(shè)置(可以設(shè)置請求下載文件的某一部分)
```
//1. 設(shè)置請求對象
//1.1 創(chuàng)建請求路徑
NSURL *url = [NSURL URLWithString:@"URL字符串"];
//1.2 創(chuàng)建可變請求對象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//1.3 拿到當(dāng)前文件的殘留數(shù)據(jù)大小
self.currentContentLength = [self FileSize];
//1.4 告訴服務(wù)器從哪個地方開始下載文件數(shù)據(jù)
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentContentLength];
NSLog(@"%@",range);
//1.5 設(shè)置請求頭
[request setValue:range forHTTPHeaderField:@"Range"];
```
3. NSURLSession對象的釋放
```
-(void)dealloc{
//在最后的時候應(yīng)該把session釋放趟卸,以免造成內(nèi)存泄露
// NSURLSession設(shè)置過代理后蹄葱,需要在最后(比如控制器銷毀的時候)調(diào)用session的invalidateAndCancel或者resetWithCompletionHandler氏义,才不會有內(nèi)存泄露
// [self.session invalidateAndCancel];
[self.session resetWithCompletionHandler:^{ NSLog(@"釋放---");
}];
}
```
4. 優(yōu)化部分
- 關(guān)于文件下載進(jìn)度的實時更新
- 方法的獨(dú)立與抽取