iOS NSURLConnection

iOS 9.0之前 ,使用NSURLConnection比較多
iOS 7.0之后,蘋果官方推出NSURLSession,并在9.0之后推薦使用且丟棄了NSURLConnection

因?yàn)镹SURLSession在異步的處理上比NSURLConnection好很多

本文作為學(xué)習(xí)和了解,詳解iOS9.0之前的NSURLConnection

NSURLConnection:
  • 從 iOS 2.0 開始,已經(jīng)有10多年的歷史了
    異步方法在 iOS 5.0 之后才有 ,
    在 iOS 5.0 之后,是通過代理的方式,來實(shí)現(xiàn)網(wǎng)絡(luò)開發(fā)

本例使用NSURLConnection、NSFileHandle、NSOutputStream
實(shí)現(xiàn)了從本地服務(wù)器下載一個700M視頻的demo

NSOutputStream示意圖

對內(nèi)存進(jìn)行了優(yōu)化,內(nèi)存一直保持在30M之內(nèi)
并用GCD+NSRunloop進(jìn)行異步下載
不影響UI主線程

內(nèi)存占用控制

并在后臺打印出下載完成進(jìn)度百分比


后臺下載完成百分比
Demo詳解:

#import "ViewController.h"

// Newsstand Kit‘s 雜志包,是專門做雜志的,主要在國外使用,因?yàn)閲鴥?nèi)的書籍盜版太嚴(yán)重
// ISBN書號,電子書也必須是唯一的
// NSURLConnectionDownloadDelegate 方法主要針對雜志的下載提供接口
// 如果在開發(fā)中,使用 NSURLConnectionDownloadDelegate 代理方法下載,能夠監(jiān)聽下載進(jìn)度,但是無法找到下載的文件
@interface ViewController () <NSURLConnectionDataDelegate>

@property (weak, nonatomic) IBOutlet UIProgressView *Progress;
@property(nonatomic,assign)long long expectedContentLength; //要下載文件的總長度
@property(nonatomic,assign)long long currentLength;         //當(dāng)前下載的長度

//@property(nonatomic,strong) NSMutableData *fileData;        //用來每次接受到數(shù)據(jù),拼接數(shù)據(jù)使用
@property(nonatomic,copy) NSString *tartgetFilePath;        //保存的目標(biāo)路徑

//@property(nonatomic,assign,getter=isFinished)BOOL finished;

@property(nonatomic,assign)CFRunLoopRef downloadRunloop;    //下載線程的運(yùn)行循環(huán)
/*
 保存文件的輸出流
 - (void)open;      寫入之前,打開流
 - (void)close;     完成之后,關(guān)閉流
 */
@property(nonatomic,strong)NSOutputStream *fileStrem;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

/**
 NSURLConnection - 從 iOS 2.0 開始,已經(jīng)有10多年的歷史了
 異步方法在 iOS 5.0 之后才有 , 在 iOS 5.0 之后,是通過代理的方式,來實(shí)現(xiàn)網(wǎng)絡(luò)開發(fā)
 那時是iOS網(wǎng)絡(luò)開發(fā)的黑暗史,網(wǎng)絡(luò)開發(fā)很難
 
 就因?yàn)檫@個黑暗史,才成就了一些第三方框架: AFN,ASI這些第三方框架
 
 NS - NextStep 公司的縮寫
 
 - 開發(fā)簡單的網(wǎng)絡(luò)請求比較方便,可以采用異步方法
 - 開發(fā)發(fā)雜的網(wǎng)絡(luò)請求,例如:大文件下載,仍然需要使用代理來開發(fā),非常繁瑣!
 
 **對NSURLConnection 的一些細(xì)節(jié)有所了解
 
 問題:1.沒有下載進(jìn)度,會影響用戶體驗(yàn)
      2.有內(nèi)存峰值,下載的文件有多大,NSData就會占用多大內(nèi)存

 解決方法:
 - 通過代理的方式解決
 
 1.進(jìn)度跟進(jìn),解決思路:
    1>在響應(yīng)方法中獲得文件總大小
    2>每次接受到數(shù)據(jù),計(jì)算獲得數(shù)據(jù)的總長度,和總大小相比,計(jì)算出百分比
 
 2.保存文件的思路
    1>保存完成寫入磁盤
            測試結(jié)果:和異步方法執(zhí)行一樣,仍然存在內(nèi)存峰值。
            推測:蘋果的異步方法的實(shí)現(xiàn)思路,和剛才的delegate實(shí)現(xiàn)思路一樣
            問題:下載的內(nèi)存峰值依舊存在
    2>下載一個寫一個
        1)NSFileHandle 徹底解決了內(nèi)存峰值問題
        2)NSOutputStream 輸出流
        - Socket 網(wǎng)絡(luò)本質(zhì)上,在客戶端和服務(wù)器之間,數(shù)據(jù)的傳遞,都是以二進(jìn)制流的方式傳遞的。
        - 對數(shù)據(jù)流的操作,一定要有一些網(wǎng)絡(luò)底層的思想之后,才容易理解
        - 操作方式,比 FindHandle 更簡潔
 */


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //1.url
        NSString *urlString = @"http://127.0.0.1/1.mp4";
        urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlString];
        
        //2.request
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //3.connection
        //開始下載時的線程,是用 dispatch_async 創(chuàng)建的
        NSLog(@"開始 %@",[NSThread currentThread]);
        //    For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.
        //為了保證連接的工作正常,調(diào)用線程的run loop 必須運(yùn)行在默認(rèn)的運(yùn)行循環(huán)模式下
        NSURLConnection *conn =  [NSURLConnection connectionWithRequest:request delegate:self];//使用代理
        
        //設(shè)置代理工作的操作隊(duì)列
        [conn setDelegateQueue:[[NSOperationQueue alloc]init]];//新的問題 默認(rèn)還在主線程工作,干擾主線程UI更新
        
        //4.啟動連接
        [conn start];
        
//        self.finished = NO;
        
        //5.啟動運(yùn)行循環(huán)
//        while (!self.isFinished) {
//            //啟動一個死循環(huán),每次監(jiān)聽0.1秒
//            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
//        }
        
        //CoreFoundation框架(純C語言,是Foundation的基礎(chǔ))中,提供了對運(yùn)行循環(huán)更底層的操作
        /*
         CFRunLoopStop(rl)    停止當(dāng)前的runloop
         CFRunLoopGetCurrent()當(dāng)前線程的runloop
         CFRunLoopRun();       直接運(yùn)行當(dāng)前線程的運(yùn)行循環(huán)
         
         以下代碼,是最標(biāo)準(zhǔn)的 runloop 的操作方法
         */
        //1.拿到當(dāng)前線程的運(yùn)行循環(huán)
        self.downloadRunloop = CFRunLoopGetCurrent();
        //2.啟動運(yùn)行循環(huán)
        CFRunLoopRun();
        
        NSLog(@"到我了嗎?");
    });
    
//直接下載視頻
//    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionErro){
//        [data writeToFile:@"/Users/Liu/Desktop/123.mp4" atomically:YES];
//        
//        NSLog(@"完成");
//        }];
    
}

#pragma mark - NSURLConnectionDataDelegate
//1.接收到服務(wù)器的響應(yīng) - 狀態(tài)行&響應(yīng)頭 - 做一些準(zhǔn)備工作
/*
 expectedContentLength 要下載文件的總大小 long long
 suggestedFilename     服務(wù)器建議保存的文件名
 */
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);
    
    //記錄文件總大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
    
    //生成目標(biāo)文件路徑
    self.tartgetFilePath = [@"/Users/Liu/Desktop" stringByAppendingPathComponent:response.suggestedFilename];
    
    //刪除文件,removeItemAtPath 如果文件存在直接刪除,如果文件不存在,什么也不做
    [[NSFileManager defaultManager]removeItemAtPath:self.tartgetFilePath error:NULL];
    
    //以追加的方式打開文件流
    self.fileStrem = [[NSOutputStream alloc]initToFileAtPath:self.tartgetFilePath append:YES];
    [self.fileStrem open];
}



//-(NSMutableData *)fileData{
//    if(_fileData == nil){
//        _fileData = [[NSMutableData alloc] init];
//    }
//    return _fileData;
//}

//2.接收到服務(wù)器的數(shù)據(jù) - 此代理方法可能會執(zhí)行多次 因?yàn)闀盏蕉鄠€data
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"接收到數(shù)據(jù)長度 %tu",data.length);
    
    self.currentLength +=data.length;
    //計(jì)算百分比
    //progress = 小 long long / 大 long long
    float progress = (float)self.currentLength/self.expectedContentLength;
    
    NSLog(@"%f %@",progress,[NSThread currentThread]);
    
    //在主線程更新進(jìn)度條
    dispatch_async(dispatch_get_main_queue(), ^{
        self.Progress.progress = progress;
    });
    
    //將數(shù)據(jù)追加到文件流中
    [self.fileStrem write:data.bytes maxLength:data.length];
    
//拼接數(shù)據(jù)
//    
//    [self.fileData appendData:data];
}

//-(void)writeToFileWithData:(NSData *)data{
//    // 文件操作
//    /*
//     NSFileManager :主要功能:創(chuàng)建目錄,檢查目錄是否存在,遍歷目錄,刪除文件。。。主要針對文件集的操作,類似于Finder
//     NSFileHandle: 文件”句柄“, 如果在開發(fā)中,看到 Handle 這個單詞,就意味著是對前面的單詞“File”進(jìn)行操作的對象
//                    主要功能:對同一個文件進(jìn)行二進(jìn)制的讀寫操作的對象
//     */
//    
//    //注意:如果文件不存在,fp 在實(shí)例化的結(jié)果是空
//    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.tartgetFilePath];
//    
//    //判斷文件是否存在 - 如果存在,則追加數(shù)據(jù);如果不存在,直接將數(shù)據(jù)寫入磁盤
//    if(fp == nil){
//        [data writeToFile:self.tartgetFilePath atomically:YES];
//    }else{
//        //如果存在,將data“追加”到現(xiàn)有文件
//        //1.將文件指針移動到文件的末尾
//        [fp seekToEndOfFile];
//        
//        //2.寫入文件
//        [fp writeData:data];
//        
//        //3.關(guān)閉文件,在C語言的開發(fā)中,凡涉及到文件讀寫,打開和關(guān)閉通常是成對實(shí)現(xiàn)的
//        [fp closeFile];
//    }
//}

//3.所有數(shù)據(jù)加載完成 - 所有數(shù)據(jù)都傳輸完畢后調(diào)用,只是最后一個通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //結(jié)束時代理工作的線程,是指定 NSOperationQueue 調(diào)度的
    NSLog(@"完成 %@",[NSThread currentThread]);
    //把數(shù)據(jù)寫入磁盤
//    [self.fileData writeToFile:self.tartgetFilePath atomically:YES];
//    //釋放 fileData
//    self.fileData = nil;
    [self.fileStrem close];
    
//    //設(shè)置結(jié)束標(biāo)記
//    self.finished = YES;
    
    //停止下載線程所在的運(yùn)行循環(huán)
    CFRunLoopStop(self.downloadRunloop);
}

//4.下載失敗或者錯誤,提示:在正常商業(yè)應(yīng)用開發(fā)中一定要做出錯誤處理
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"失敗 %@",error);
}
@end


PS:本文使用的下載源在本地服務(wù)器127.0.0.1,根據(jù)需要可以修改成其他下載源。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容