使用NSURLConnection下載文件并使用NSOutputStream保存在本地

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)求伦仍,步驟非常繁瑣

方式一.直接使用NSURLConnectionsendAsynchronousRequest方法發(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è)最大的峰值


一次性寫入磁盤會(huì)出現(xiàn)一次峰值.png

我測(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(@"完成");
    }
    `
    流程是什么樣的握联?如下圖
請(qǐng)求及數(shù)據(jù)發(fā)送過(guò)程.png
  1. 調(diào)用 [connection start];方法發(fā)起請(qǐng)求連接
  2. 調(diào)用代理方法connection: didReceiveResponse:得到響應(yīng)頭(包含文件的大小和文件名稱(suggestedFilename))和狀態(tài)行:
    響應(yīng)頭.png
  3. 調(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];

使用輸入流寫入數(shù)據(jù).png

到這又出現(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)題了

Demo:
https://github.com/wasterd/FileDownloadDemo.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阿蝶,一起剝皮案震驚了整個(gè)濱河市雳锋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羡洁,老刑警劉巖玷过,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異筑煮,居然都是意外死亡辛蚊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門真仲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)袋马,“玉大人,你說(shuō)我怎么就攤上這事秸应÷橇荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵软啼,是天一觀的道長(zhǎng)桑谍。 經(jīng)常有香客問(wèn)我,道長(zhǎng)祸挪,這世上最難降的妖魔是什么霉囚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上盈罐,老公的妹妹穿的比我還像新娘榜跌。我一直安慰自己,他們只是感情好盅粪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布钓葫。 她就那樣靜靜地躺著,像睡著了一般票顾。 火紅的嫁衣襯著肌膚如雪础浮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天奠骄,我揣著相機(jī)與錄音豆同,去河邊找鬼。 笑死含鳞,一個(gè)胖子當(dāng)著我的面吹牛影锈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝉绷,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鸭廷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了熔吗?” 一聲冷哼從身側(cè)響起辆床,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桅狠,沒(méi)想到半個(gè)月后讼载,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡中跌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年维雇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晒他。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逸贾,靈堂內(nèi)的尸體忽然破棺而出陨仅,到底是詐尸還是另有隱情,我是刑警寧澤铝侵,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布灼伤,位于F島的核電站,受9級(jí)特大地震影響咪鲜,放射性物質(zhì)發(fā)生泄漏狐赡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一疟丙、第九天 我趴在偏房一處隱蔽的房頂上張望颖侄。 院中可真熱鬧鸟雏,春花似錦、人聲如沸览祖。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)展蒂。三九已至又活,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锰悼,已是汗流浹背柳骄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箕般,地道東北人耐薯。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像隘世,于是被迫代替她去往敵國(guó)和親可柿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容