使用NSURLConnection實(shí)現(xiàn)下載
1. 小文件下載
- 第一種方式(NSData)
//使用NSDta直接加載網(wǎng)絡(luò)上的url資源(不考慮線(xiàn)程)
-(void)dataDownload {
//1.確定資源路徑
NSURL *url = [NSURL URLWithString:@"http://tupian.qqjay.com/u/2013/1127/19_222949_14.jpg"];
//2.根據(jù)URL加載對(duì)應(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ā)送異步請(qǐng)求下載文件資源
-(void)connectDownload {
//1.確定請(qǐng)求路徑
NSURL *url = [NSURL URLWithString:@"http://tupian.qqjay.com/u/2013/1127/19_222949_14.jpg"];
//2.創(chuàng)建請(qǐng)求對(duì)象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.使用NSURLConnection發(fā)送一個(gè)異步請(qǐng)求
[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ā)送異步請(qǐng)求的方式下載文件
-(void)connectionDelegateDownload {
//1.確定請(qǐng)求路徑
NSURL *url = [NSURL URLWithString:@"下載文件的URL地址"];
//2.創(chuàng)建請(qǐng)求對(duì)象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.使用NSURLConnection設(shè)置代理并發(fā)送異步請(qǐng)求
[NSURLConnection connectionWithRequest:request delegate:self];
}
pragma mark--NSURLConnectionDataDelegate
//當(dāng)接收到服務(wù)器響應(yīng)的時(shí)候調(diào)用,該方法只會(huì)調(diào)用一次
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//創(chuàng)建一個(gè)容器,用來(lái)接收服務(wù)器返回的數(shù)據(jù)
self.fileData = [NSMutableData data];
//獲得當(dāng)前要下載文件的總大小(通過(guò)響應(yīng)頭得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
self.totalLength = res.expectedContentLength;
NSLog(@"%zd",self.totalLength);
//拿到服務(wù)器端推薦的文件名稱(chēng)
self.fileName = res.suggestedFilename;
}
//當(dāng)接收到服務(wù)器返回的數(shù)據(jù)時(shí)會(huì)調(diào)用
//該方法可能會(huì)被調(diào)用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// NSLog(@"%s",__func__);
//拼接每次下載的數(shù)據(jù)
[self.fileData appendData:data];
//計(jì)算當(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ò)請(qǐng)求結(jié)束之后調(diào)用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//文件下載完畢把接受到的文件數(shù)據(jù)寫(xiě)入到沙盒中保存
//1.確定要保存文件的全路徑
//caches文件夾路徑
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fullPath = [caches stringByAppendingPathComponent:self.fileName];
//2.寫(xiě)數(shù)據(jù)到文件中
[self.fileData writeToFile:fullPath atomically:YES];
NSLog(@"%@",fullPath);
}
//當(dāng)請(qǐng)求失敗的時(shí)候調(diào)用該方法
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"%s",__func__);
}
2. 大文件下載
- 實(shí)現(xiàn)思路
邊接收數(shù)據(jù)邊寫(xiě)文件以解決內(nèi)存越來(lái)越大的問(wèn)題 - 核心代碼
//當(dāng)接收到服務(wù)器響應(yīng)的時(shí)候調(diào)用,該方法只會(huì)調(diào)用一次
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//0.獲得當(dāng)前要下載文件的總大小(通過(guò)響應(yīng)頭得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
self.totalLength = res.expectedContentLength;
NSLog(@"%zd",self.totalLength);
//創(chuàng)建一個(gè)新的文件,用來(lái)當(dāng)接收到服務(wù)器返回?cái)?shù)據(jù)的時(shí)候往該文件中寫(xiě)入數(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)建一個(gè)空的文件
[manager createFileAtPath:fullPath contents:nil attributes:nil];
}
//當(dāng)接收到服務(wù)器返回的數(shù)據(jù)時(shí)會(huì)調(diào)用
//該方法可能會(huì)被調(diào)用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//1.創(chuàng)建一個(gè)用來(lái)向文件中寫(xiě)數(shù)據(jù)的文件句柄
//注意當(dāng)下載完成之后,該文件句柄需要關(guān)閉,調(diào)用closeFile方法
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath];
//2.設(shè)置寫(xiě)數(shù)據(jù)的位置(追加)
[handle seekToEndOfFile];
//3.寫(xiě)數(shù)據(jù) [handle writeData:data];
//4.計(jì)算當(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ù)傳
- 實(shí)現(xiàn)思路
在下載文件的時(shí)候不再是整塊的從頭開(kāi)始下載,而是看當(dāng)前文件已經(jīng)下載到哪個(gè)地方,然后從該地方接著往后面下載。可以通過(guò)在請(qǐng)求對(duì)象中設(shè)置請(qǐng)求頭實(shí)現(xiàn)。 - 解決方案(設(shè)置請(qǐng)求頭)
//2.創(chuàng)建請(qǐng)求對(duì)象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//2.1 設(shè)置下載文件的某一部分
// 只要設(shè)置HTTP請(qǐng)求頭的Range屬性, 就可以實(shí)現(xiàn)從指定位置開(kāi)始下載
/*
表示頭500個(gè)字節(jié):Range: bytes=0-499
表示第二個(gè)500字節(jié):Range: bytes=500-999
表示最后500個(gè)字節(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)前要下載文件的總大小(通過(guò)響應(yīng)頭得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
//注意點(diǎn):res.expectedContentLength獲得是本次請(qǐng)求要下載的文件的大小(并非是完整的文件的大小)
//因此:文件的總大小 == 本次要下載的文件大小+已經(jīng)下載的文件的大小
self.totalLength = res.expectedContentLength + self.currentLength;
NSLog(@"----------------------------%zd",self.totalLength);
//0 判斷當(dāng)前是否已經(jīng)下載過(guò),如果當(dāng)前文件已經(jīng)存在,那么直接返回
if (self.currentLength >0) { return; }
輸出流
使用輸出流也可以實(shí)現(xiàn)和NSFileHandle相同的功能
如何使用
//1.創(chuàng)建一個(gè)數(shù)據(jù)輸出流
/*
第一個(gè)參數(shù):二進(jìn)制的流數(shù)據(jù)要寫(xiě)入到哪里
第二個(gè)參數(shù):采用什么樣的方式寫(xiě)入流數(shù)據(jù),如果YES則表示追加,如果是NO則表示覆蓋
*/
NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:fullPath append:YES];
//只要調(diào)用了該方法就會(huì)往文件中寫(xiě)數(shù)據(jù)
//如果文件不存在,那么會(huì)自動(dòng)的創(chuàng)建一個(gè)
[stream open];
self.stream = stream;
//2.當(dāng)接收到數(shù)據(jù)的時(shí)候?qū)憯?shù)據(jù)
//使用輸出流寫(xiě)數(shù)據(jù)
/*
第一個(gè)參數(shù):要寫(xiě)入的二進(jìn)制數(shù)據(jù)
第二個(gè)參數(shù):要寫(xiě)入的數(shù)據(jù)的大小
*/
[self.stream write:data.bytes maxLength:data.length];
//3.當(dāng)文件下載完畢的時(shí)候關(guān)閉輸出流
//關(guān)閉輸出流
[self.stream close];
self.stream = nil;
- 使用多線(xiàn)程下載文件的思路
- 01 開(kāi)啟多條線(xiàn)程,每條線(xiàn)程都只下載文件的一部分(通過(guò)設(shè)置請(qǐng)求頭中的Range來(lái)實(shí)現(xiàn))
- 02 創(chuàng)建一個(gè)和需要下載文件大小一致的文件,判斷當(dāng)前是那個(gè)線(xiàn)程,根據(jù)當(dāng)前的線(xiàn)程來(lái)判斷下載的數(shù)據(jù)應(yīng)該寫(xiě)入到文件中的哪個(gè)位置。(假設(shè)開(kāi)5條線(xiàn)程來(lái)下載10M的文件,那么線(xiàn)程1下載0-2M,線(xiàn)程2下載2-4M一次類(lèi)推,當(dāng)接收到服務(wù)器返回的數(shù)據(jù)之后應(yīng)該先判斷當(dāng)前線(xiàn)程是哪個(gè)線(xiàn)程,假如當(dāng)前線(xiàn)程是線(xiàn)程2,那么在寫(xiě)數(shù)據(jù)的時(shí)候就從文件的2M位置開(kāi)始寫(xiě)入)
- 03 代碼相關(guān):使用NSFileHandle這個(gè)類(lèi)的seekToFileOfSet方法,來(lái)向文件中特定的位置寫(xiě)入數(shù)據(jù)。
- 04 技術(shù)相關(guān)
a.每個(gè)線(xiàn)程通過(guò)設(shè)置請(qǐng)求頭下載文件中的某一個(gè)部分
b.通過(guò)NSFileHandle向文件中的指定位置寫(xiě)數(shù)據(jù)
使用NSURLSession實(shí)現(xiàn)下載
1. NSURLSession下載文件-代理
- 創(chuàng)建NSURLSession對(duì)象,設(shè)置代理(默認(rèn)配置)
//1.創(chuàng)建NSURLSession,并設(shè)置代理
/*
第一個(gè)參數(shù):session對(duì)象的全局配置設(shè)置,一般使用默認(rèn)配置就可以
第二個(gè)參數(shù):誰(shuí)成為session對(duì)象的代理
第三個(gè)參數(shù):代理方法在哪個(gè)隊(duì)列中執(zhí)行(在哪個(gè)線(xiàn)程中調(diào)用),如果是主隊(duì)列那么在主線(xiàn)程中執(zhí)行,如果是非主隊(duì)列,那么在子線(xiàn)程中執(zhí)行
*/
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
- 根據(jù)Session對(duì)象創(chuàng)建一個(gè)NSURLSessionDataTask任務(wù)(post和get選擇)
//創(chuàng)建task
NSURL *url = [NSURL URLWithString:@"http://tupian.qqjay.com/u/2013/1127/19_222949_14.jpg"];
//注意:如果要發(fā)送POST請(qǐng)求,那么請(qǐng)使用dataTaskWithRequest,設(shè)置一些請(qǐng)求頭信息
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url];
- 執(zhí)行任務(wù)(其它方法,如暫停、取消等)
//啟動(dòng)task
//[dataTask resume];
//其它方法,如取消任務(wù),暫停任務(wù)等
//[dataTask cancel];
//[dataTask suspend];
```
4. 遵守代理協(xié)議,實(shí)現(xiàn)代理方法(3個(gè)相關(guān)的代理方法)
```
/*
1.當(dāng)接收到服務(wù)器響應(yīng)的時(shí)候調(diào)用
session:發(fā)送請(qǐng)求的session對(duì)象
dataTask:根據(jù)NSURLSession創(chuàng)建的task任務(wù)
response:服務(wù)器響應(yīng)信息(響應(yīng)頭)
completionHandler:通過(guò)該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ù)時(shí)調(diào)用 該方法可能會(huì)被調(diào)用多次
*/
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data
/*
3.當(dāng)請(qǐng)求完成之后調(diào)用該方法 不論是請(qǐng)求成功還是請(qǐng)求失敗都調(diào)用該方法,如果請(qǐng)求失敗,那么error對(duì)象有值,否則那么error對(duì)象為空
*/
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
```
5. 當(dāng)接收到服務(wù)器響應(yīng)的時(shí)候,告訴服務(wù)器接收數(shù)據(jù)(調(diào)用block)
```
//默認(rèn)情況下,當(dāng)接收到服務(wù)器響應(yīng)之后,服務(wù)器認(rèn)為客戶(hù)端不需要接收數(shù)據(jù),所以后面的代理方法不會(huì)調(diào)用
//如果需要繼續(xù)接收服務(wù)器返回的數(shù)據(jù),那么需要調(diào)用block,并傳入對(duì)應(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實(shí)現(xiàn)大文件下載
1. 使用NSURLSession和NSURLSessionDownload可以很方便的實(shí)現(xiàn)文件下載操作
```
/*
第一個(gè)參數(shù):要下載文件的url路徑
第二個(gè)參數(shù):當(dāng)接收完服務(wù)器返回的數(shù)據(jù)之后調(diào)用該block
location:下載的文件的保存地址(默認(rèn)是存儲(chǔ)在沙盒中tmp文件夾下面,隨時(shí)會(huì)被刪除)
response:服務(wù)器響應(yīng)信息,響應(yīng)頭
error:該請(qǐng)求的錯(cuò)誤信息
*/
//說(shuō)明:downloadTaskWithURL方法已經(jīng)實(shí)現(xiàn)了在下載文件數(shù)據(jù)的過(guò)程中邊下載文件數(shù)據(jù),邊寫(xiě)入到沙盒文件的操作
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * __nullable location, NSURLResponse * __nullable response, NSError * __nullable error)
```
2. downloadTaskWithURL內(nèi)部默認(rèn)已經(jīng)實(shí)現(xiàn)了變下載邊寫(xiě)入操作,所以不用開(kāi)發(fā)人員擔(dān)心內(nèi)存問(wèn)題
3. 文件下載后默認(rèn)保存在tmp文件目錄,需要開(kāi)發(fā)人員手動(dòng)的剪切到合適的沙盒目錄
4. 缺點(diǎn):沒(méi)有辦法監(jiān)控下載進(jìn)度
###3. 使用NSURLSessionDownloadTask實(shí)現(xiàn)大文件下載-監(jiān)聽(tīng)下載進(jìn)度
1. 創(chuàng)建NSURLSession并設(shè)置代理,通過(guò)NSURLSessionDownloadTask并以代理的方式來(lái)完成大文件的下載
```
//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. 常用代理方法的說(shuō)明
```
/*
1.當(dāng)接收到下載數(shù)據(jù)的時(shí)候調(diào)用,可以在該方法中監(jiān)聽(tīng)文件下載的進(jìn)度
該方法會(huì)被調(diào)用多次
totalBytesWritten:已經(jīng)寫(xiě)入到文件中的數(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ù)下載的時(shí)候調(diào)用該方法
fileOffset:恢復(fù)之后,要從文件的什么地方開(kāi)發(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.請(qǐng)求完成之后調(diào)用
如果請(qǐng)求失敗,那么error有值
*/
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
```
3. 實(shí)現(xiàn)斷點(diǎn)下載相關(guān)代碼
```
//如果任務(wù),取消了那么以后就不能恢復(fù)了
// [self.downloadTask cancel];
//如果采取這種方式來(lái)取消任務(wù),那么該方法會(huì)通過(guò)resumeData保存當(dāng)前文件的下載信息
//只要有了這份信息,以后就可以通過(guò)這些信息來(lái)恢復(fù)下載
[self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) { self.resumeData = resumeData;}];
//繼續(xù)下載
//首先通過(guò)之前保存的resumeData信息,創(chuàng)建一個(gè)下載任務(wù)
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume];
```
4. 計(jì)算當(dāng)前下載進(jìn)度
```
//獲取文件下載進(jìn)度
self.progress.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;
```
5. 局限性
- 如果用戶(hù)點(diǎn)擊暫停之后退出程序,那么需要把恢復(fù)下載的數(shù)據(jù)寫(xiě)一份到沙盒,代碼復(fù)雜度更高
- 如果用戶(hù)在下載中途未保存恢復(fù)下載數(shù)據(jù)即退出程序,則不具備可操作性
###4. 使用NSURLSessionDataTask實(shí)現(xiàn)大文件離線(xiàn)斷點(diǎn)下載(完整)
1. 關(guān)于NSOutputStream的使用
```
//1. 創(chuàng)建一個(gè)輸入流,數(shù)據(jù)追加到文件的屁股上
//把數(shù)據(jù)寫(xiě)入到指定的文件地址,如果當(dāng)前文件不存在,則會(huì)自動(dòng)創(chuàng)建
NSOutputStream *stream = [[NSOutputStream alloc]initWithURL:[NSURL fileURLWithPath:[self fullPath]] append:YES];
//2. 打開(kāi)流
[stream open];
//3. 寫(xiě)入流數(shù)據(jù)
[stream write:data.bytes maxLength:data.length];
//4.當(dāng)不需要的時(shí)候應(yīng)該關(guān)閉流
[stream close];
```
2. 關(guān)于網(wǎng)絡(luò)請(qǐng)求請(qǐng)求頭的設(shè)置(可以設(shè)置請(qǐng)求下載文件的某一部分)
```
//1. 設(shè)置請(qǐng)求對(duì)象
//1.1 創(chuàng)建請(qǐng)求路徑
NSURL *url = [NSURL URLWithString:@"URL字符串"];
//1.2 創(chuàng)建可變請(qǐng)求對(duì)象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//1.3 拿到當(dāng)前文件的殘留數(shù)據(jù)大小
self.currentContentLength = [self FileSize];
//1.4 告訴服務(wù)器從哪個(gè)地方開(kāi)始下載文件數(shù)據(jù)
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentContentLength];
NSLog(@"%@",range);
//1.5 設(shè)置請(qǐng)求頭
[request setValue:range forHTTPHeaderField:@"Range"];
```
3. NSURLSession對(duì)象的釋放
```
-(void)dealloc{
//在最后的時(shí)候應(yīng)該把session釋放,以免造成內(nèi)存泄露
// NSURLSession設(shè)置過(guò)代理后,需要在最后(比如控制器銷(xiāo)毀的時(shí)候)調(diào)用session的invalidateAndCancel或者resetWithCompletionHandler,才不會(huì)有內(nèi)存泄露
// [self.session invalidateAndCancel];
[self.session resetWithCompletionHandler:^{ NSLog(@"釋放---");
}];
}
```
4. 優(yōu)化部分
- 關(guān)于文件下載進(jìn)度的實(shí)時(shí)更新
- 方法的獨(dú)立與抽取