NSURLConnection 是iOS 2.0開始
異步加載--是iOS 5.0才有的,在5.0之前是通過代理來實現(xiàn)網(wǎng)絡(luò)開發(fā)
---在開發(fā)簡單的網(wǎng)絡(luò)請求還是挺方便的,直接使用異步方法
---但是在開發(fā)復(fù)雜的網(wǎng)絡(luò)請求,步驟非常繁瑣
方式一.直接使用NSURLConnection的sendAsynchronousRequest方法發(fā)起異步請求:
代碼如下:
NSString *urlStr = @"http://localhost/001--等一分鐘.mp4"; NSString * url = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; `` [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
//這一步是把數(shù)據(jù)寫入磁盤,data首先是保存在內(nèi)存中然后再一起寫入磁盤 [data writeToFile:@"/Users/xxxxx/Desktop/保存數(shù)據(jù)/123.wmv" atomically:YES]; }]
這種方式下載視頻有兩個問題
1.沒有下載進度,會影響用戶體驗
2.內(nèi)存偏高,有一個最大的峰值

我測試的時候的峰值非??植?,達(dá)到了1.96G
解決思路:
1.通過代理方式來解決
進度:首先在響應(yīng)方法中獲得文件總大?。?br>
其次每次接收數(shù)據(jù),計算數(shù)據(jù)的總比例: 每次接收的數(shù)據(jù)拼接/文件總大小
2.保存文件:
a.保存完,寫入磁盤
b.邊下載邊保存
1.使用NSURLConnectionDelegate來解決上面的“沒有下載進度”的問題:代碼如下:
注意: 這個NSURLConnectionDownloadDelegate代理方法千萬別亂用,專用于雜志的下載提供接口!能夠監(jiān)聽下載進度,但是無法拿到下載的內(nèi)容;目前國內(nèi)雜志的app還是比較少,國外比較流行
/** 要下載文件總大小 /
@property(nonatomic ,assign)long long expectedContentLength;
/* 當(dāng)前下載的大小 */
@property(nonatomic ,assign)long long currentLength;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlStr = @"http://localhost/001--消息發(fā)送機制.mp4"; NSString * url = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; NSLog(@"開始"); NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self ]; //啟動連接 [connection start];
}
//1.接收服務(wù)器響應(yīng)-狀態(tài)行&響應(yīng)頭
(void)connection:(NSURLConnection )connection didReceiveResponse:(NSURLResponse )response
{
/
響應(yīng)頭返回的數(shù)據(jù)一般有:建議的下載視頻名稱:textEncodingName
下載視頻總大小:expectedContentLength
*/
self.expectedContentLength = response.expectedContentLength;
self.currentLength = 0;
}
//接收到服務(wù)器的數(shù)據(jù),此方法可能會執(zhí)行很多次-
(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{//當(dāng)前下載視頻的大小
self.currentLength = data.length;float progress = (float)self.currentLength / self.expectedContentLength;
}
//數(shù)據(jù)接收完成時調(diào)用此代理
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"完成");
}
`
流程是什么樣的?如下圖

- 調(diào)用
[connection start];方法發(fā)起請求連接 - 調(diào)用代理方法
connection: didReceiveResponse:得到響應(yīng)頭(包含文件的大小和文件名稱(suggestedFilename))和狀態(tài)行:
響應(yīng)頭.png - 調(diào)用
connection:didReceiveData:方法來接收數(shù)據(jù),因為數(shù)據(jù)在網(wǎng)絡(luò)中傳輸是以二進制數(shù)據(jù)的形式進行傳輸?shù)模@里的data是發(fā)送的很多一段的二進制數(shù)據(jù),下載完后拼接在一起,然后寫入磁盤,所有這個代理方法會被多次調(diào)用;用 當(dāng)前接收的數(shù)據(jù)大小/總的數(shù)據(jù)大小 = 進度,
那么進度的問題解決了,接下來是解決寫入磁盤時出現(xiàn)峰值的問題
我在這使用了NSOutputStream輸出流以文件的方式追加到輸出流中,很簡單只需三步即可完成:
1.創(chuàng)建文件流&打開文件流
//創(chuàng)建輸出流, 以文件追加的方式寫入流中
self.outStream = [[NSOutputStream alloc]initToFileAtPath:self.targetFilePath append:YES ];
[self.outStream open];
2.寫入文件流
[self.outStream write:data.bytes maxLength:data.length];
3.關(guān)閉文件流
[self.outStream close];

到這又出現(xiàn)新的問題:默認(rèn)NSURLConnection是在主線程工作,指定了代理的工作隊列之后,
[connection setDelegateQueue:[[NSOperationQueue alloc]init]];
整個下載仍然是在主線程?。。?!UI事件會卡住文件下載
注意:在看到 NSURLConnection中的描述“ For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.”,這句話的意思是: 為了保證連接的正常工作,調(diào)用線程的RunLoop 必須運行在默認(rèn)的運行循環(huán)模式下!!--- 這也是iOS9之后丟棄NSURLConnection的原因
那么接下來如何解決呢?
使用GCD來創(chuàng)建dispatch_async(dispatch_get_global_queue(0, 0), ^{},把請求設(shè)置放block中:
·
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSString *urlStr = @"http://localhost/001--消息發(fā)送機制.mp4";
NSString * url = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSLog(@"開始");
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self ];
[connection setDelegateQueue:[[NSOperationQueue alloc]init]];
//啟動連接
[connection start];
});·
但是這有個問題會出現(xiàn),代理NSURLConnectionDelegate的方法不會走了??!這是為什么呢,因為這個線程 dispatch_async(dispatch_get_global_queue(0, 0), ^{})出了括號(線程的作用域)后就銷毀了。
那么我們?nèi)绾谓鉀Q這個問題呢?
其實很簡單---手動啟動runloop運行循環(huán)就可以解決了
/** 下載線程的運行循環(huán) */
@property(assign,nonatomic)CFRunLoopRef downloadRunloop;
在 dispatch_async(dispatch_get_global_queue(0, 0), ^{})的block中啟動拿到runloop
self.downloadRunloop = CFRunLoopGetCurrent();
啟動runloop
CFRunLoopRun();
在connectionDidFinishLoading代理方法中,停止下載線程所在的runloop
CFRunLoopStop(self.downloadRunloop);
這樣就解決了卡主線程的問題了
