NSURLConnection 作為 Core Foundation / CFNetwork 架構的 API 之上的一個抽象,在 2003 年,隨著第一版的 Safari 的發布就發布了。NSURLConnection 這個名字,實際上是指代的 Foundation 架構的 URL 載入系統中一系列有關聯的組件:NSURLRequest、NSURLResponse、NSURLProtocol、 NSURLCache、 NSHTTPCookieStorage、NSURLCredentialStorage 以及同名類 NSURLConnection。
NSURLRequest 被傳遞給 NSURLConnection。被委派物件(遵守以前的非正式協議 <NSURLConnectionDelegate> 和 <NSURLConnectionDataDelegate>)非同步地返回一個 NSURLResponse 以及包含伺服器返回資訊的 NSData。
在一個請求被發送到伺服器之前,系統會先查詢共用的緩衝資訊,然後根據策略(policy)以及可用性(availability)的不同,一個已經被緩衝的響應可能會被立即返回。如果沒有緩衝的響應可用,則這個請求將根據我們指定的策略來緩衝它的響應以便將來的請求可以使用。
在把請求發送給伺服器的過程中,伺服器可能會發出鑒權查詢(authentication challenge),這可以由共用的 cookie 或機密儲存(credential storage)來自動響應,或者由被委派物件來響應。發送中的請求也可以被註冊的 NSURLProtocol 對象所攔截,以便在必要的時候無縫地改變其載入行為。
不管怎樣,NSURLConnection 作為網路基礎架構,已經服務了成千上萬的 iOS 和 Mac OS 程式,並且做的還算相當不錯。但是這些年,一些用例——尤其是在 iPhone 和 iPad 上面——已經對 NSURLConnection 的幾個核心概念提出了挑戰,讓蘋果有理由對它進行重構。
一、NSURLConnection的常用類
(1)NSURL:請求地址
(2)NSURLRequest:封裝一個請求,儲存發給伺服器的全部資料,包括一個NSURL對象,要求方法、要求標頭、請求體....
(3)NSMutableURLRequest:NSURLRequest的子類
(4)NSURLConnection:負責發送請求,建立用戶端和伺服器的串連。發送NSURLRequest的資料給伺服器,並收集來自伺服器的響應資料
二、NSURLConnection的使用
1.簡單說明
使用NSURLConnection發送請求的步驟很簡單
(1)建立一個NSURL對象,佈建要求路徑(佈建要求路徑)
(2)傳入NSURL建立一個NSURLRequest對象,佈建要求頭和請求體(建立請求對象)
(3)使用NSURLConnection發送NSURLRequest(發送請求)
2.程式碼範例
(1)發送請求的三個步驟:
1.佈建要求路徑
2.建立請求對象
3.發送請求
3.1發送同步請求(一直在等待伺服器返回資料,這行代碼會卡住,如果伺服器,沒有返回資料,那麼在主線程UI會卡住不能繼續執行操作)有傳回值
3.2發送非同步請求:沒有傳回值
說明:任何NSURLRequest預設都是get請求。
(2)發送同步請求程式碼範例:
複製代碼 代碼如下:
//
// YYViewController.m
// 01-NSURLConnection的使用(GET)
//
// Created by apple on 14-6-28.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#import "YYViewController.h"
#import "MBProgressHUD+MJ.h"
@interface YYViewController ()
@property (weak, nonatomic) IBOutlet UITextField *username;
@property (weak, nonatomic) IBOutlet UITextField *pwd;
- (IBAction)login;
@end
複製代碼 代碼如下:
@implementation YYViewController
- (IBAction)login {
// 1.提前的表單驗證
if (self.username.text.length==0) {
[MBProgressHUD showError:@"請輸入使用者名稱"];
return;
}
if (self.pwd.text.length==0) {
[MBProgressHUD showError:@"請輸入密碼"];
return;
}
// 2.發送請求給伺服器(帶上帳號和密碼)
//添加一個遮罩,禁止使用者操作
// [MBProgressHUD showMessage:@"正在努力載入中...."];
// GET請求:請求行\要求標頭\請求體
//
// 1.佈建要求路徑
NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.53:8080/MJServer/login?username=%@&pwd=%@",self.username.text,self.pwd.text];
NSURL *url=[NSURL URLWithString:urlStr];
// 2.建立請求對象
NSURLRequest *request=[NSURLRequest requestWithURL:url];
// 3.發送請求
//發送同步請求,在主線程執行
NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//(一直在等待伺服器返回資料,這行代碼會卡住,如果伺服器沒有返回資料,那麼在主線程UI會卡住不能繼續執行操作)
NSLog(@"--%d--",data.length);
}
@end
模擬器情況:
列印伺服器返回的資訊:
補充說明:
1.提前的表單驗證
2.發送請求給伺服器(帶上帳號和密碼)
GET請求:請求行\要求標頭\請求體
注意:GET請求中不存在請求體,因為所有的資訊都寫在URL裡面。在IOS裡面,請求行和要求標頭都不用寫。
(3)發送非同步請求
發送非同步請求有兩種方式:
1)使用block回調
2)代理
A.使用block回調方法發送非同步請求
使用block回調程式碼範例:
複製代碼 代碼如下:
//
// YYViewController.m
// 01-NSURLConnection的使用(GET)
//
// Created by apple on 14-6-28.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#import "YYViewController.h"
#import "MBProgressHUD+MJ.h"
@interface YYViewController ()
@property (weak, nonatomic) IBOutlet UITextField *username;
@property (weak, nonatomic) IBOutlet UITextField *pwd;
- (IBAction)login;
@end
複製代碼 代碼如下:
@implementation YYViewController
- (IBAction)login {
// 1.提前的表單驗證
if (self.username.text.length==0) {
[MBProgressHUD showError:@"請輸入使用者名稱"];
return;
}
if (self.pwd.text.length==0) {
[MBProgressHUD showError:@"請輸入密碼"];
return;
}
// 2.發送請求給伺服器(帶上帳號和密碼)
//添加一個遮罩,禁止使用者操作
[MBProgressHUD showMessage:@"正在努力載入中...."];
//
// 1.佈建要求路徑
NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.53:8080/MJServer/login?username=%@&pwd=%@",self.username.text,self.pwd.text];
NSURL *url=[NSURL URLWithString:urlStr];
// 2.建立請求對象
NSURLRequest *request=[NSURLRequest requestWithURL:url];
// 3.發送請求
//3.1發送同步請求,在主線程執行
// NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//(一直在等待伺服器返回資料,這行代碼會卡住,如果伺服器沒有返回資料,那麼在主線程UI會卡住不能繼續執行操作)
//3.1發送非同步請求
//建立一個隊列(預設添加到該隊列中的任務非同步執行)
// NSOperationQueue *queue=[[NSOperationQueue alloc]init];
//擷取一個主隊列
NSOperationQueue *queue=[NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"--block回調資料--%@---%d", [NSThread currentThread],data.length);
//隱藏HUD,重新整理UI的操作一定要放在主線程執行
[MBProgressHUD hideHUD];
//解析data
/*
{"success":"登入成功"}
{"error":"使用者名稱不存在"}
{"error":"密碼不正確"}
*/
NSDictionary *dict=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"%@",dict);
//判斷後,在介面提示登入資訊
NSString *error=dict[@"error"];
if (error) {
[MBProgressHUD showError:error];
}else
{
NSString *success=dict[@"success"];
[MBProgressHUD showSuccess:success];
}
}];
NSLog(@"請求發送完畢");
}
@end
模擬器情況(注意這裡使用了第三方架構):
列印查看:
代碼說明:
block程式碼片段:當伺服器有返回資料的時候調用會開一條新的線程去發送請求,主線程繼續往下走,當拿到伺服器的返回資料的資料的時候再回調block,執行block程式碼片段。這種情況不會卡住主線程。
隊列的作用:決定這個block操作放在哪個線程執行?
重新整理UI介面的操作應該放在主線程執行,不能放在子線程,在子線程處理UI相關操作會出現一些莫名的問題。
提示:
(1)建立一個操作,放在NSOperation隊列中執行,預設是非同步執行的。
(2)mainqueue 返回一個和主線程相關的隊列,即主隊列。
新的問題:如果向伺服器發送請求,卻並沒有拿到資料,那麼程式會崩潰(data不可為空)
改進代碼:
複製代碼 代碼如下:
NSOperationQueue *queue=[NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
//當請求結束的時候調用(有兩種結果,一個是成功拿到資料,也可能沒有拿到資料,請求失敗)
NSLog(@"--block回調資料--%@---%d", [NSThread currentThread],data.length);
//隱藏HUD,重新整理UI的操作一定要放在主線程執行
[MBProgressHUD hideHUD];
//解析data
/*
{"success":"登入成功"}
{"error":"使用者名稱不存在"}
{"error":"密碼不正確"}
*/
if (data) {//請求成功
NSDictionary *dict=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"%@",dict);
//判斷後,在介面提示登入資訊
NSString *error=dict[@"error"];
if (error) {
[MBProgressHUD showError:error];
}else
{
NSString *success=dict[@"success"];
[MBProgressHUD showSuccess:success];
}
}else //請求失敗
{
[MBProgressHUD showError:@"網路繁忙,請稍後重試!"];
}
}];
解析data
複製代碼 代碼如下:
//解析data
/*
{"success":"登入成功"}
{"error":"使用者名稱不存在"}
{"error":"密碼不正確"}
*/
說明:使用NSJSONSerialization 返回的對象,取決於最外層是什麼,如果是{}那就是字典,[]那就是數組等。
補充說明:
首先確定請求路徑,然後建立請求對象(預設發送的時get請求),使用非同步方法呼叫(一調用這個方法,它會自動開啟一個子線程去發送請求,當請求成功,資料返回的時候自動調用內部的程式碼片段,這個程式碼片段在那個線程執行取決於隊列,如果是主隊列,那麼在子線程發送請求成功拿到伺服器的資料後,回到主線程中解析資料,重新整理UI介面)。
B.使用代理方法發送非同步請求
要監聽伺服器返回的data,所以使用<NSURLConnectionDataDelegate>協議
常見大代理方法如下:
複製代碼 代碼如下:
#pragma mark- NSURLConnectionDataDelegate代理方法
//當接收到伺服器的響應(連通了伺服器)時會調用
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
//當接收到伺服器的資料時會調用(可能會被調用多次,每次只傳遞部分資料)
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
//當伺服器的資料載入完畢時就會調用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
//請求錯誤(失敗)的時候調用(請求逾時\斷網\沒有網\,一般指用戶端錯誤)
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
使用非同步方法呼叫發送get請求的程式碼範例:
複製代碼 代碼如下:
//
// YYViewController.m
// 01-NSURLConnection的使用(GET)
//
// Created by apple on 14-6-28.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#import "YYViewController.h"
#import "MBProgressHUD+MJ.h"
@interface YYViewController ()<NSURLConnectionDataDelegate>
@property (weak, nonatomic) IBOutlet UITextField *username;
@property (weak, nonatomic) IBOutlet UITextField *pwd;
@property(nonatomic,strong)NSMutableData *responseData;
- (IBAction)login;
@end
複製代碼 代碼如下:
@implementation YYViewController
- (IBAction)login {
// 1.提前的表單驗證
if (self.username.text.length==0) {
[MBProgressHUD showError:@"請輸入使用者名稱"];
return;
}
if (self.pwd.text.length==0) {
[MBProgressHUD showError:@"請輸入密碼"];
return;
}
// 2.發送請求給伺服器(帶上帳號和密碼)
//添加一個遮罩,禁止使用者操作
[MBProgressHUD showMessage:@"正在努力載入中...."];
//
// 2.1佈建要求路徑
NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.53:8080/MJServer/login?username=%@&pwd=%@",self.username.text,self.pwd.text];
NSURL *url=[NSURL URLWithString:urlStr];
// 2.2建立請求對象
// NSURLRequest *request=[NSURLRequest requestWithURL:url];//預設就是GET請求
//佈建要求逾時
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url];
request.timeoutInterval=5.0;
// 2.3.發送請求
//使用代理髮送非同步請求(通常應用於檔案下載)
NSURLConnection *conn=[NSURLConnection connectionWithRequest:request delegate:self];
[conn start];
NSLog(@"已經發出請求---");
}
#pragma mark- NSURLConnectionDataDelegate代理方法
/*
*當接收到伺服器的響應(連通了伺服器)時會調用
*/
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"接收到伺服器的響應");
//初始化資料
self.responseData=[NSMutableData data];
}
/*
*當接收到伺服器的資料時會調用(可能會被調用多次,每次只傳遞部分資料)
*/
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"接收到伺服器的資料");
//拼接資料
[self.responseData appendData:data];
NSLog(@"%d---%@--",self.responseData.length,[NSThread currentThread]);
}
/*
*當伺服器的資料載入完畢時就會調用
*/
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"伺服器的資料載入完畢");
//隱藏HUD
[MBProgressHUD hideHUD];
//處理伺服器返回的所有資料
NSDictionary *dict=[NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:nil];
//判斷後,在介面提示登入資訊
NSString *error=dict[@"error"];
if (error) {
[MBProgressHUD showError:error];
}else
{
NSString *success=dict[@"success"];
[MBProgressHUD showSuccess:success];
}
NSLog(@"%d---%@--",self.responseData.length,[NSThread currentThread]);
}
/*
*請求錯誤(失敗)的時候調用(請求逾時\斷網\沒有網\,一般指用戶端錯誤)
*/
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// NSLog(@"請求錯誤");
//隱藏HUD
[MBProgressHUD hideHUD];
[MBProgressHUD showError:@"網路繁忙,請稍後重試!"];
}
@end
列印查看:
補充:
(1)資料的處理
在didReceiveData:方法中,拼接接收到的所有資料,等所有資料都拿到後,在connectionDidFinishLoading:方法中進行處理
(2)網路延遲
在做網路開發的時候,一定要考慮到網路延遲情況的處理,可以在伺服器的代碼設定一個斷點類比。
在伺服器代碼的登入方法中設定斷點
佈建要求的最大延遲
模擬器情況:
列印查看:
三、NSMutableURLRequest
NSMutableURLRequest是NSURLRequest的子類,常用方法有
佈建要求逾時等待時間(超過這個時間就算逾時,請求失敗)
複製代碼 代碼如下:
- (void)setTimeoutInterval:(NSTimeInterval)seconds;
佈建要求方法(比如GET和POST)
複製代碼 代碼如下:
- (void)setHTTPMethod:(NSString *)method;
佈建要求體
複製代碼 代碼如下:
- (void)setHTTPBody:(NSData *)data;
佈建要求頭
複製代碼 代碼如下:
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;