NSURLConnection 是iOS 2.0開(kāi)始
異步加載--是iOS 5.0才有的仰剿,在5.0之前是通過(guò)代理來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)開(kāi)發(fā)
---在開(kāi)發(fā)簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求還是挺方便的酸些,直接使用異步方法
---但是在開(kāi)發(fā)復(fù)雜的網(wǎng)絡(luò)請(qǐng)求伦仍,步驟非常繁瑣
方式一.直接使用NSURLConnection
的sendAsynchronousRequest
方法發(fā)起異步請(qǐng)求:
代碼如下:
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]; }]
這種方式下載視頻有兩個(gè)問(wèn)題
1.沒(méi)有下載進(jìn)度车荔,會(huì)影響用戶體驗(yàn)
2.內(nèi)存偏高锻弓,有一個(gè)最大的峰值
我測(cè)試的時(shí)候的峰值非常恐怖蔫骂,達(dá)到了1.96G
解決思路:
1.通過(guò)代理方式來(lái)解決
進(jìn)度:首先在響應(yīng)方法中獲得文件總大行姆尽纠吴!
其次每次接收數(shù)據(jù),計(jì)算數(shù)據(jù)的總比例: 每次接收的數(shù)據(jù)拼接/文件總大小
2.保存文件:
a.保存完慧瘤,寫入磁盤
b.邊下載邊保存
1.使用NSURLConnectionDelegate來(lái)解決上面的“沒(méi)有下載進(jìn)度”的問(wèn)題:代碼如下:
注意: 這個(gè)NSURLConnectionDownloadDelegate
代理方法千萬(wàn)別亂用戴已,專用于雜志的下載提供接口!能夠監(jiān)聽(tīng)下載進(jìn)度锅减,但是無(wú)法拿到下載的內(nèi)容糖儡;目前國(guó)內(nèi)雜志的app還是比較少,國(guó)外比較流行
/** 要下載文件總大小 /
@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ā)送機(jī)制.mp4"; NSString * url = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; NSLog(@"開(kāi)始"); NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self ]; //啟動(dòng)連接 [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ù)怔匣,此方法可能會(huì)執(zhí)行很多次-
(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{//當(dāng)前下載視頻的大小
self.currentLength = data.length;
float progress = (float)self.currentLength / self.expectedContentLength;
}
//數(shù)據(jù)接收完成時(shí)調(diào)用此代理
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"完成");
}
`
流程是什么樣的握联?如下圖
- 調(diào)用
[connection start];
方法發(fā)起請(qǐng)求連接 - 調(diào)用代理方法
connection: didReceiveResponse:
得到響應(yīng)頭(包含文件的大小和文件名稱(suggestedFilename))和狀態(tài)行:
- 調(diào)用
connection:didReceiveData:
方法來(lái)接收數(shù)據(jù),因?yàn)閿?shù)據(jù)在網(wǎng)絡(luò)中傳輸是以二進(jìn)制數(shù)據(jù)的形式進(jìn)行傳輸?shù)拿柯鳎@里的data是發(fā)送的很多一段的二進(jìn)制數(shù)據(jù)金闽,下載完后拼接在一起,然后寫入磁盤剿骨,所有這個(gè)代理方法會(huì)被多次調(diào)用代芜;用 當(dāng)前接收的數(shù)據(jù)大小/總的數(shù)據(jù)大小 = 進(jìn)度,
那么進(jìn)度的問(wèn)題解決了浓利,接下來(lái)是解決寫入磁盤時(shí)出現(xiàn)峰值的問(wèn)題
我在這使用了NSOutputStream輸出流以文件的方式追加到輸出流中挤庇,很簡(jiǎn)單只需三步即可完成:
1.創(chuàng)建文件流&打開(kāi)文件流
//創(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)新的問(wèn)題:默認(rèn)NSURLConnection是在主線程工作,指定了代理的工作隊(duì)列之后嫡秕,
[connection setDelegateQueue:[[NSOperationQueue alloc]init]];
整個(gè)下載仍然是在主線程?视铩!@パ省驾凶!UI事件會(huì)卡住文件下載
注意:在看到 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 必須運(yùn)行在默認(rèn)的運(yùn)行循環(huán)模式下!!--- 這也是iOS9之后丟棄NSURLConnection的原因
那么接下來(lái)如何解決呢潮改?
使用GCD來(lái)創(chuàng)建dispatch_async(dispatch_get_global_queue(0, 0), ^{}
狭郑,把請(qǐng)求設(shè)置放block中:
·
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSString *urlStr = @"http://localhost/001--消息發(fā)送機(jī)制.mp4";
NSString * url = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSLog(@"開(kāi)始");
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self ];
[connection setDelegateQueue:[[NSOperationQueue alloc]init]];
//啟動(dòng)連接
[connection start];
});·
但是這有個(gè)問(wèn)題會(huì)出現(xiàn),代理NSURLConnectionDelegate的方法不會(huì)走了;阍凇翰萨!這是為什么呢,因?yàn)檫@個(gè)線程 dispatch_async(dispatch_get_global_queue(0, 0), ^{})出了括號(hào)(線程的作用域)后就銷毀了糕殉。
那么我們?nèi)绾谓鉀Q這個(gè)問(wèn)題呢亩鬼?
其實(shí)很簡(jiǎn)單---手動(dòng)啟動(dòng)runloop運(yùn)行循環(huán)就可以解決了
/** 下載線程的運(yùn)行循環(huán) */
@property(assign,nonatomic)CFRunLoopRef downloadRunloop;
在 dispatch_async(dispatch_get_global_queue(0, 0), ^{})
的block中啟動(dòng)拿到runloop
self.downloadRunloop = CFRunLoopGetCurrent();
啟動(dòng)runloop
CFRunLoopRun();
在connectionDidFinishLoading
代理方法中,停止下載線程所在的runloop
CFRunLoopStop(self.downloadRunloop);
這樣就解決了卡主線程的問(wèn)題了