iOS學習筆記12-網絡(一)NSURLConnection

一、網絡請求

在網絡開發中,需要了解一些常用的請求方法:
  • GET請求:get是獲取數據的意思,數據以明文在URL中傳遞,受限於URL長度,所以傳輸數據量比較小。
  • POST請求:post是向服務器提交數據的意思,提交的數據以實際內容形式存放到消息頭中進行傳遞,無法在瀏覽器url中查看到,大小沒有限制。
  • HEAD請求:請求頭信息,並不返回請求數據體,而只返回請求頭信息,常用用於在文件下載中取得文件大小、類型等信息。

Web請求

二、NSURLConnection

NSURLConnection是蘋果提供的原生網絡訪問類,但是蘋果很快會將其廢棄,且由NSURLSession(iOS7以後)來替代。目前使用最廣泛的第三方網絡框架AFNetworking最新版本已棄用了NSURLConnection,那我們學習它還有什麼用呢?
* 首先,蘋果棄用它還是需要時間的,最起碼到iOS10之後。
* 現在還有一些老項目會使用NSURLConnection,特別是2013年之前的項目,用戶量基礎還是很大的;
* 另外,不得不承認,有些公司還在用類似ASI這些經典的網絡框架,所以還是很有必要學習NSURLConnection的。

讓我們來首先了解幾個類:

1. NSURL:請求地址,定義一個網絡資源路徑
NSURL *url = [NSURL URLWithString:@"協議://主機地址/路徑?參數&參數"];
解釋如下:
  • 協議:不同的協議,代表着不同的資源查找方式、資源傳輸方式,比如常用的httpftp
  • 主機地址:存放資源的主機的IP地址(域名)
  • 路徑:資源在主機中的具體位置
  • 參數:參數可有可無,也可以多個。如果帶參數的話,用“?”號後面接參數,多個參數的話之間用&隔開
2.NSURLRequest:請求,根據前面的NSURL建立一個請求
NSURLRequest *request = [NSURLRequest requestWithURL:url 
                                         cachePolicy:NSURLRequestUseProtocolCachePolicy 
                                     timeoutInterval:15.0];
參數解釋如下:
  • url:資源路徑
  • cachePolicy:緩存策略(無論使用哪種緩存策略,都會在本地緩存數據),類型爲枚舉類型,取值如下:
NSURLRequestUseProtocolCachePolicy = 0 //默認的緩存策略,使用協議的緩存策略
NSURLRequestReloadIgnoringLocalCacheData = 1 //每次都從網絡加載
NSURLRequestReturnCacheDataElseLoad = 2 //返回緩存否則加載,很少使用
NSURLRequestReturnCacheDataDontLoad = 3 //只返回緩存,沒有也不加載,很少使用
  • timeoutInterval:超時時長,默認60s
另外,還可以設置其它一些信息,比如請求頭,請求體等等,如下:
NSMutableURLRequest *request = 
          [NSMutableURLRequest requestWithURL:url 
                                  cachePolicy:NSURLRequestUseProtocolCachePolicy 
                              timeoutInterval:15.0];
// 告訴服務器數據爲json類型
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 
// 設置請求體body(json類型的數據)
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} 
                                                   options:NSJSONWritingPrettyPrinted 
                                                     error:nil];
request.HTTPBody = jsonData; 

注意,上面的request是NSMutableURLRequest,即可變類型

3.NSURLResponse:請求結果響應,連接成功後服務器會返回的響應
  • 該類不用我們創建,連接後服務器返回的,裏面包含了一些響應信息
下面我們來發個完整的網絡請求:
#pragma mark 發送數據請求
- (void)sendRequest{
    NSString *urlStr = [NSString stringWithFormat:@"http://192.168.1.208/FileDownload.aspx?file=%@",_textField.text];
    //注意對於url中的中文是無法解析的,需要進行url編碼(指定編碼類型爲utf-8)
    //另外注意url解碼使用stringByRemovingPercentEncoding方法
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    //創建url鏈接,該鏈接是下載文件
    NSURL *url = [NSURL URLWithString:urlStr];
    //創建請求
    NSURLRequest *request = [NSURLRequest requestWithURL:url 
                                             cachePolicy:NSURLRequestUseProtocolCachePolicy 
                                         timeoutInterval:15.0f];
    //創建連接,設置請求,以及設置代理
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    //啓動連接,用start啓動的連接都是異步請求
    [connection start];
}
要得到請求的數據,需要實現NSURLConnection的代理方法:
#pragma mark - 連接代理方法
#pragma mark 開始響應
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    _data = [[NSMutableData alloc] init];
    _progressView.progress = 0;
    //通過響應頭中的Content-Length取得整個響應的總長度
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    NSDictionary *httpResponseHeaderFields = [httpResponse allHeaderFields];
    _totalLength = [[httpResponseHeaderFields objectForKey:@"Content-Length"] longLongValue];
}
#pragma mark 接收響應數據(根據響應內容的大小此方法會被重複調用)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    //連續接收數據
    [_data appendData:data];
    //更新進度
    [self updateProgress];
}
#pragma mark 數據接收完成
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //數據接收完保存文件(注意蘋果官方要求:下載數據只能保存在緩存目錄)
    NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    savePath = [savePath stringByAppendingPathComponent:_textField.text];
    [_data writeToFile:savePath atomically:YES];
}
#pragma mark 請求失敗
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    //如果連接超時或者連接地址錯誤可能就會報錯
}

網絡請求下載
但這樣每次都要去實現這些代理,感覺十分麻煩,不用怕,NSURLConnection有簡化方法,這也是我們用的最多得。

typedef void (^CompletionBlock)(NSURLResponse*, NSData*, NSError*);
/* 發送一個異步請求 */
+ (void)sendAsynchronousRequest:(NSURLRequest *)request /*請求*/
                          queue:(NSOperationQueue *)queue /* 連接所在線程隊列 */
              completionHandler:(CompletionBlock)completion;/* 請求回調 */
/* 發送一個同步請求 */
+ (void)sendSynchronousRequest:(NSURLRequest *)request /*請求*/
                          queue:(NSOperationQueue *)queue /* 連接所在線程隊列 */
              completionHandler:(CompletionBlock)completion;/* 請求回調 */
這樣我們就不用實現代理方法了,下面是簡化方法的使用實例:
#pragma mark 發送數據請求
- (void)sendRequest{
    NSString *urlStr = [NSString stringWithFormat:@"%@",kURL];
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    //創建url鏈接
    NSURL *url = [NSURL URLWithString:urlStr];
    /*創建可變請求*/
    NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:5.0f];
    [requestM setHTTPMethod:@"POST"];//設置位post請求
    //創建post參數
    NSString *bodyDataStr = [NSString stringWithFormat:@"userName=%@&password=%@",_userName,_password];
    NSData *bodyData = [bodyDataStr dataUsingEncoding:NSUTF8StringEncoding];
    [requestM setHTTPBody:bodyData];
    //發送一個異步請求
    [NSURLConnection sendAsynchronousRequest:requestM 
                                       queue:[NSOperationQueue mainQueue] 
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (!error) {
            //加載數據
            [self loadData:data];
            //刷新表格
            [_tableView reloadData];
        }
    }];
}

三、進階-文件分段下載

實際開發文件下載的時候,不管是通過代理方法還是靜態方法執行請求和響應,我們都會分批請求數據,而不是一次性請求數據。

假設一個文件有1G,那麼只要每次請求1M的數據,請求1024次也就下載完了。那麼如何讓服務器每次只返回1M的數據呢?
  • 在網絡開發中可以在請求的頭文件中設置一個range信息,它代表請求數據的大小。
  • 在WEB開發中我們還有另一種請求方法“HEAD”,通過這種請求服務器只會響應頭信息,其他數據不會返回給客戶端,這樣一來整個數據的大小也就可以得到了。
首先我們需要先獲取要下載的文件大小:
#pragma mark  取得文件大小
- (long long)getFileTotlaLength:(NSString *)fileName{
    NSURL *url = [self getDownloadUrl:fileName];//獲取URL,這個方法我就不列出來了
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url 
                                            cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                        timeoutInterval:5.0f];
    //設置爲頭信息請求
    [request setHTTPMethod:@"HEAD"];
    NSURLResponse *response;
    NSError *error;
    //注意這裏使用了同步請求,直接將文件大小返回
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    //取得內容長度
    return response.expectedContentLength;
}
我們還需要封裝一個下載指定數據大小的請求:
#pragma mark 下載指定塊大小的數據
- (void)downloadFile:(NSString *)fileName startByte:(long long)start endByte:(long long)end{
    NSString *range = [NSString stringWithFormat:@"Bytes=%lld-%lld",start,end];
    NSURL *url = [self getDownloadUrl:fileName];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url 
                                      cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                  timeoutInterval:5.0f];
    //通過請求頭設置數據請求範圍
    [request setValue:range forHTTPHeaderField:@"Range"];
    NSURLResponse *response = nil;
    NSError *error = nil;
    //注意這裏使用同步請求,避免文件塊追加順序錯誤
    NSData *data = [NSURLConnection sendSynchronousRequest:request 
                                         returningResponse:&response 
                                                     error:&error];
    if(!error){
        [self fileAppend:[self getSavePath:fileName] data:data];
    }
}
最後我們來下載整個文件:
#pragma mark 異步下載文件
- (void)downloadFileAsync{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self downloadFile];
    });
}
#pragma mark 文件下載,拼接每個下載小文件
- (void)downloadFile{
    // 獲取要下載的文件總大小
    _totalLength = [self getFileTotlaLength:_textField.text];
    _loadedLength = 0;
    long long startSize = 0;
    long long endSize = 0;
    //分段下載
    while(startSize < _totalLength){
        //kFILE_BLOCK_SIZE 宏定義的是分段下載的字節大小
        endSize = startSize + kFILE_BLOCK_SIZE - 1;
        if (endSize > _totalLength) {
            endSize = _totalLength - 1;
        }
        [self downloadFile:_textField.text startByte:startSize endByte:endSize];
        //更新進度
        _loadedLength += (endSize - startSize) + 1;
        [self updateProgress];
        startSize += kFILE_BLOCK_SIZE;
    }
}

分段下載

四、進階-文件上傳

要實現文件上傳,需要採用POST請求,請求數據類型必須爲multipart/form-data

我們常見得請求數據類型有:
  • application/x-www-form-urlencoded:默認值,發送前對所有發送數據進行url編碼,支持瀏覽器訪問,通常文本內容提交常用這種方式。
  • multipart/form-data:多部分表單數據,支持瀏覽器訪問,不進行任何編碼,通常用於文件傳輸(此時傳遞的是二進制數據) 。
  • text/plain:普通文本數據類型,支持瀏覽器訪問,發送前其中的空格替換爲“+”,但是不對特殊字符編碼。
  • application/json:json數據類型 。
  • text/xml:xml數據類型。

我們需要自己定製上傳請求,以下爲定製過程:

1. 上傳請求頭必須滿足如下格式:

上傳請求頭

2. 請求體內容要求如下面格式:
--boundary
Content-Disposition:form-data;name=”表單控件名稱”;filename=”上傳文件名稱”
Content-Type:文件MIME Types

文件二進制數據;

--boundary--

上傳請求體

至於上面上傳文件的MIME Types類型,我列出幾個常用的:
  • *.gif文件 - image/gif
  • *.html文件 - text/html
  • *.jpg文件 - image/jpeg
  • *.mov文件 - video/quicktime
  • *.mp3文件 - audio/mpeg
  • *.pdf文件 - application/pdf
  • *.txt文件 - text/plain
  • *.xml文件 - text/xml
  • *.avi文件 - video/x-msvideo
下面爲上傳文件具體實例:
#pragma mark 上傳文件
-(void)uploadFile{
    NSString *fileName = _textField.text;
    NSURL *url = [self getUploadUrl:fileName];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
                                      cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                  timeoutInterval:5.0f];
    request.HTTPMethod = @"POST";
    NSData *data = [self getHttpBody:fileName];
    //通過請求頭設置
    NSString *lengthStr = [NSString stringWithFormat:@"%lu",(unsigned long)data.length];
    [request setValue:lengthStr forHTTPHeaderField:@"Content-Length"];
    NSString *typeStr = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBOUNDARY_STRING];
    [request setValue:typeStr forHTTPHeaderField:@"Content-Type"];
    //設置數據體
    request.HTTPBody = data;
    //發送異步請求
    [NSURLConnection sendAsynchronousRequest:request 
                             queue:[[NSOperationQueue alloc]init] 
                 completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if(error){
            NSLog(@"error:%@",connectionError.localizedDescription);
        }
    }];
}
#pragma mark 取得數據體
-(NSData *)getHttpBody:(NSString *)fileName{
    NSMutableData *dataM = [NSMutableData data];
    NSString *type = [self getMIMETypes:fileName];
    //構建請求體body的頂部
    NSMutableString *bodyTop = [NSMutableString string];
    //宏kBOUNDARY_STRING就是boundary標示
    [bodyTop appendFormat:@"--%@\n",kBOUNDARY_STRING];
    [bodyTop appendFormat:@"Content-Disposition: form-data; name=\"file1\"; filename=\"%@\"\n",fileName];
    [bodyTop appendFormat:@"Content-Type: %@\n\n",type];
    //構建請求體body的底部
    NSString *bodyBottom = [NSString stringWithFormat:@"\n--%@--",kBOUNDARY_STRING];
    NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
    //構建請求體body中間的二進制上傳數據
    NSData *fileData = [NSData dataWithContentsOfFile:filePath];
    //把頂部、數據、底部組合起來,形成body
    [dataM appendData:[bodyTop dataUsingEncoding:NSUTF8StringEncoding]];
    [dataM appendData:fileData];
    [dataM appendData:[bodyBottom dataUsingEncoding:NSUTF8StringEncoding]];
    return dataM;
}
有什麼問題隨時可以在下方評論提出!O(∩_∩)O哈!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章