1.小文件的下載
由于文件較小,我們可以直接可以使用NSURLConnection的異步請求(默認在這里開了一條線程,不回阻塞主線程)方法,在block里面進行數據接收:
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
if (!connectionError) {
dispatch_async(dispatch_get_main_queue(), ^{
_imageView.image = [[UIImage alloc] initWithData:data];
});
} else {
NSLog(@"erorr = %@",[connectionError description]);
}
}];
可是大文件下載我們一般就不采用這種方式了,這種方式下載文件看不到進度,并且雖然在這里開了子線程來進行下載,可是在主線程的UI刷新是要等到服務器返回了總的文件data才會進行,這就給用戶體驗曹成了不好的地方,而且在大的文件下載過程中有可能會涉及到斷點下載的情況(比如斷網或者手動暫停)。
2.大文件的下載
具體思路是初始化一個NSMutableData類型的_responseData,在下載的過程中在- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
代理方法里面進行數據拼接,每次接收到數據就append,在代理方法- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
里面進行文件的寫入,至于進度監聽,可以在每次接收到數據的時候算目前接受到的總長度_currentDownLoadLength,然后除以文件的response.expectedContentLength,斷點下載的關鍵是設置當前請求的請求頭:
//設置請求頭
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",_currentDownLoadLength];
[request setValue:range forHTTPHeaderField:@"Range"];
這里有一個新的東西:http協議請求頭里面的Range,Range是請求頭里面的下載起點(或者說下載的進度范圍 ):
//Range 可以指定每次從網絡下載數據包的大小
bytes = 0 - 499 //從0到499共500
bytes = 500 - //從500到結束
bytes = -500 //最后500
bytes = 500 - 599, 800 - 899 //同時指定幾個范圍
我們可以在暫停或者斷網的情況下,記錄當前的下載總長度,然后設置新的請求頭,并重新初始化一個NSURLConnection對象,并在代理方法里面繼續進行數據接收。
具體實現代理如下:
#pragma mark --NSURLConnectionDataDelegate
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *dbPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:_fileName];
//下載完成 寫入文件
[_responseData writeToFile:dbPath atomically:YES];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
_fileName = response.suggestedFilename;
_totalLength = response.expectedContentLength;
NSLog(@"fileName = %@",response.suggestedFilename);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//拼接每次接受的數據
[_responseData appendData:data];
//計算目前接收到的數據總長度
_currentDownLoadLength = _currentDownLoadLength + data.length;
//下載進度百分比
NSString *subStr = @"%";
NSLog(@"進度 = %@%.2f",subStr, 100.0 *_currentDownLoadLength /_totalLength);
}
#pragma mark --private Method
- (void)buttonAction:(id)sender {
UIButton *button = (UIButton *)sender;
button.selected = !button.selected;
//斷點下載
if (button.selected) {
//設置請求頭
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",_currentDownLoadLength];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://download.xmcdn.com/group18/M01/BC/91/wKgJKlfAEN6wZgwhANQvLrUQ3Pg146.aac"]];
[request setValue:range forHTTPHeaderField:@"Range"];
//重新請求
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
[_connection start];
} else {
//暫停
[_connection cancel];
_connection = nil;
}
}
下載完成之后將_repsonseData寫入sandBox,效果如下:
那么問題又來了,有些童鞋可能會發現,加入一個文件很大的情況下,會出現內存暴漲的情況,從而使程序報memory warning的情況,這樣的代碼是不完整的,也是質量不高的,于是我們要解決內存暴漲的問題
3.解決大文件內存暴漲問題
思路:我們注意到_responseData存在于內存中,每次append的是時候,內存就逐漸暴漲,于是我們可以開多個子線程來下載同一個文件,并且在下載的過程中可以邊下載邊寫入沙盒sandBox,這就需要在接收到服務器響應的時候,開多個connection對象來下載(平分區域設置請求頭的下載范圍),使用NSFileHandel文件句柄寫入沙盒,在finishload代理方法里面進行文件的合并處理,具體實現代碼如下:
#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
if ([connection isEqual:_connection]) {
//獲取總長度
_totalWriteDataLength = response.expectedContentLength;
//取消
[_connection cancel];
NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
//開4個connection同時進行下載
for (int i = 0; i < 4; i++) {
//4個文件
NSString *filePath = [cachePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%d", response.suggestedFilename, i]];
//創建4個臨時文件
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createFileAtPath:filePath contents:nil attributes:nil];
//創建請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://download.xmcdn.com/group18/M01/BC/91/wKgJKlfAEN6wZgwhANQvLrUQ3Pg146.aac"]];
//平分每個Connection的下載范圍
NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", response.expectedContentLength/4*i, response.expectedContentLength/4*(i+1)];
[request setValue:range forHTTPHeaderField:@"Range"];
//創建多個請求
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
//創建4個文件句柄
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[_pathArray addObject:filePath];
[_connectionArray addObject:connection];
[_fileHandleArry addObject:fileHandle];
}
}
}
在finishload代理方法里面進行文件合并處理:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_finishedCount++;
NSInteger index = [_connectionArray indexOfObject:connection];
//獲取句柄
NSFileHandle *fileHandle = [_fileHandleArry objectAtIndex:index];
[fileHandle closeFile];
fileHandle = nil;
if (_finishedCount == 4)//將4個任務下載的文件合并成一個
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *tmpPath = [_pathArray objectAtIndex:index];
NSString *filePath = [tmpPath substringToIndex:tmpPath.length];
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil];
//創建一個文件句柄
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
for (int i = 0; i < 4; i++) {
[fileHandle seekToEndOfFile];
//向總文件寫入數據
[fileHandle writeData:[NSData dataWithContentsOfFile:[_pathArray objectAtIndex:i]]];
}
[fileHandle closeFile];
fileHandle = nil;
}
}
我們可以看內存不會存在暴漲的情況了,說明了這一些代碼是有意義的,目前關于NSURLConnection的下載介紹就是這些,由于本人技術有限,寫的不好的地方請大家指正,如果覺得本人對于您有幫助的話,請動用您寶貴的雙手點個贊,謝謝!
作者------mrChan1234