iOS開發-網路-合理封裝請求介面
概述
如今大多App都會與網路打交道,作為開發者,合理的對網路後臺請求介面進行封裝十分重要。本文要介紹的就是一種常見的採用回撥函式(方法)的網路介面封裝,也算的是一種構架吧。
這個構架主要的idea是這樣的,把所有的介面封裝成一個類,在工程中隨時可以呼叫。並且利用代理Delegate構建回撥方法(callBack),工程中隨處可以通過回撥方法監聽網路請求的反饋,也就是說,一旦得到了伺服器反饋的資料,回撥函式中的程式碼就(才)會被啟用。網路請求基於AFNetworking(AFNetworking,非常有名的網路請求第三方類庫),請求均為非同步。如此構架,非常靈活很容易擴充套件和複用。
講解
要想使用本文介紹的構架,你首先需要掌握代理(Delegate),如果你不熟悉代理,這個構架對你來說將會很不解。對於不熟悉代理的同學們,建議你們去看一下資料。網路請求其實說白了就是和伺服器做一個數據互動,App把請求資料發給伺服器,伺服器返回給App一個反饋資料。請先看一下這個構架的示意圖,如下:
如上圖,這個構架的主要節點有三個,封裝網路請求的類(介面類)、使用網路請求的類(圖中的ViewController)、和伺服器。
Ok~故事是這樣的,一個夜黑風高的...醉醺醺ViewController走在湖邊,為了找回被關在雲端的Data,他苦練數載終於參透了《介面類》,天地無情,今天是時候做個了斷了。
於是乎他從懷中拿出了傳說中的“介面類”,使用內力,例項化了一個介面類的物件,接著口中念出“介面類例項.delegate=self”,拔出利劍在身旁實現了“介面類”中的一個代理方法。然後呼叫介面的方法,方法通過內嵌的AFNetworking,向伺服器發出了一道請求。又是一陣夜風吹過,三兩枯葉瑟瑟落下。ViewController酣意漸濃閉上了眼睛,現在他能做的唯有等待...
鏡頭一轉,月色中,在天上,在雲端的伺服器,ViewController剛才發出的請求正在興風作浪,雲端值夜班的眾神絲毫不敢怠慢各個健步如飛,從資料庫中搜索著能化解這道請求的神器。
此時,ViewController睏意漸濃,眼皮似墜了千金重物,意識也漸漸模糊。突然雲端顯出異像,ViewController頓時醒了過來,隱約可以看到,雲端有資料絲絲縷縷的流動,而自己懷中的“介面類”內嵌的AFNetworking也變得熾熱起來,HTTP反饋block像是要爆炸一樣的顫動著。ViewController豆大的汗珠從額頭滾下,再也不能淡定,口中叨咕著,快了,快來了... 一個霹靂,剛才用劍實現的代理方法金光一閃刺得ViewController捂住了雙眼。
一切都回歸安靜後,ViewController睜開眼睛,發現Data安靜的躺在代理方法的裡面...
程式碼示例
下面通過一個例子,來介紹一下。
開啟Xcode我建了一個SingleViewApp,然後把AFNetworking載入進工程,如下圖:
我們 OpenWeatherMap提供的天氣預報的API作為例子,簡單地利用上述構架,做一個天氣預報的App
我們來看一下這個介面怎麼用,很簡單:
引數:q=城市名字
返回Json:
{"coord":{"lon":116.4,"lat":39.91},"sys":{"type":1,"id":7405,"message":0.013,"country":"CN","sunrise":1435870233,"sunset":1435924003},"weather":[{"id":800,"main":"Clear","description":"Sky is Clear","icon":"01d"}],"base":"stations","main":{"temp":305.43,"pressure":1008,"humidity":28,"temp_min":302.15,"temp_max":308.71},"visibility":10000,"wind":{"speed":2,"deg":0},"clouds":{"all":0},"dt":1435900364,"id":1816670,"name":"Beijing","cod":200}
為了簡單我們的Demo App就只顯示 天氣和溫度,UI如下圖:
簡單直觀,點選不同城市名字命名的按鈕,在Label中顯示其天氣狀況,關於UI不是今天討論的重點,我們主要討論網路和介面。
現在開始重頭戲:介面類
新建一個類我把它命名為“Net”類,繼承NSObject,並匯入"AFNetworking.h"標頭檔案:
// // Net.h // NetInterface // // Created by Oliver on 15/7/3. // #import <Foundation/Foundation.h> #import "AFNetworking.h" @interface Net : NSObject @end
這個類就是我們一直提到的介面類,我們要吧所有的網路介面都寫到這個類裡面。現在寫一個天氣預報介面作為例子。為天氣預報介面在Net類裡宣告一個例項方法,由於這個介面需要傳得引數只有一個城市名稱,在Net類的H檔案所以方法宣告如下:
/** * 獲得某城市的天氣 * * @param cityName 城市名稱 */ -(void)getWeatherInfoWithCity:(NSString *)cityName;
一起看起來都很美好對不對?那麼現在我要提一點,可能會被大家忽略的因素。由於我們實際開發的App呼叫介面的次數可能會很多,而且呼叫介面的類也很多,所以,Net這個類將會被多次的例項化,那麼很有可能App的網路層會變得很亂更有甚者會出Bug。所以,像這樣的介面類,我們有必要將它做成單例的,整個App共享一個介面類的例項。Ok,下面就來介紹獲取單例的方法:
在H檔案宣告獲取單例的方法:
/** * 獲取Net類的單例 * * @return Net類的單例 例項(物件) */ +(Net *)getInstance;
接下來我們在Net.m檔案實現獲取單例方法:(因為所有的介面請求都是HPPT請求,會用到AFNetworking的AFHTTPRequestOperationManager,所以我在getInstace方法裡面把Manager也單例了)
#import "Net.h" __strong static AFHTTPRequestOperationManager *AFHTTPMgr; __strong static Net *NetInstance=nil; @implementation Net +(Net *)getInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ NetInstance= [[Net alloc]init];//初始化例項 //一下是AFHTTPOerrationManager的配置 AFHTTPMgr=[AFHTTPRequestOperationManager manager]; //申明返回的結果是json型別 AFHTTPMgr.responseSerializer=[AFJSONResponseSerializer serializer]; //申明請求的資料是json型別 AFHTTPMgr.requestSerializer=[AFJSONRequestSerializer serializer]; //如果報接受型別不一致請替換一致text/xml或別的 //AFHTTPMgr.responseSerializer.acceptableContentTypes= [NSSet setWithObject:@"text/xml"]; //設定超時時間 AFHTTPMgr.requestSerializer.timeoutInterval=5; }); return NetInstance; } @end
上面程式碼中,因為很變數的操作是在Block中做的,而block中不能對block外的變數進行重新更改,所以在程式的實現之前,聲明瞭:
__strong static AFHTTPRequestOperationManager *AFHTTPMgr;
__strong static Net *NetInstance=nil;
以便在單例的Block裡面對其進行更改。
接下啦,我們就可以繼續去實現介面的方法getWeatherInfoWithCity:
-(void)getWeatherInfoWithCity:(NSString *)cityName{ //介面地址 NSString *url=[NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/weather"]; //引數 NSDictionary *parameters=[[NSDictionary alloc]initWithObjectsAndKeys:cityName,@"q", nil]; //發請求 [AFHTTPMgr GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) { //請求成功Block } failure:^(AFHTTPRequestOperation *operation, NSError *error) { //請求失敗Blick }]; }
如上程式碼所示,這就是我們獲取天氣預報的介面,AFNetworking的請求成功和請求失敗的回撥Block我們暫且空著,因為,我們要設定了Delegate再用。為什麼我們要用代理而不是直接把想做的事情放在AFNetworking的Block裡面呢?
答案其實顯而易見,Block是輕量級的程式碼塊,雖然使用簡單,但是非常的封閉,與外部(Block外)進行資料交換的能力非常的有限。比如我們天氣預報的例子,我們的ViewController類希望通過伺服器返回的天氣資訊,改變UILabel的資訊,而這個資料又在Net這個類的Block裡面,沒辦法傳遞給ViewController,這就讓局面變得非常尷尬。所以我們要使用代理Delegate。其實Delegate的核心的作用就是來實現類之間的資料傳遞。現在請你,再次看一下上面的那張架構示意圖,我想你會對其有更深的理解。
下面,宣告Net類的代理,H檔案的程式碼如下:
在匯入標頭檔案宣告和@interface之間 用@protocol宣告代理
// Net.h // NetInterface #import <Foundation/Foundation.h> #import "AFNetworking.h" //代理 @protocol NetDelegate <NSObject> /** * 代理回撥方法 * * @param feedbackInfo 伺服器返回的資料 */ -(void)getWeatherInfoSuccessFeedback:(id)feedbackInfo; -(void)getWeatherInfoFailFeedback:(id)failInfo; @end @interface Net : NSObject @property (nonatomic,strong) id<NetDelegate> delegate; /** * 獲取Net類的單例 * * @return Net類的單例 例項(物件) */ +(Net *)getInstance; /** * 獲得某城市的天氣 * * @param cityName 城市名稱 */ -(void)getWeatherInfoWithCity:(NSString *)cityName; @end
如上程式碼,這是Net類的完整地H檔案,我們在代理部分,聲明瞭兩個方法,一個請求成功、一個請求失敗。在代理中申明的代理方法,我們不用去實現它,而是在M檔案總直接使用它。如果自己要使用的代理我們需要將代理宣告為自己的成員變數:
@property (nonatomic,strong) id<NetDelegate> delegate;
OK,現在讓我們回到getWeatherInfoWithCirt:方法,在Block中使用代理方法。程式碼如下:
-(void)getWeatherInfoWithCity:(NSString *)cityName{ //介面地址 NSString *url=[NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/weather"]; //引數 NSDictionary *parameters=[[NSDictionary alloc]initWithObjectsAndKeys:cityName,@"q", nil]; //發請求 [AFHTTPMgr GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) { //請求成功Block //將返回資料傳入代理方法 [self.delegate getWeatherInfoSuccessFeedback:responseObject]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { //請求失敗Blick //將錯誤資訊傳入代理方法 [self.delegate getWeatherInfoFailFeedback:error]; }]; }
OK, 如果你一路跟下來,恭喜你,你的方法類構建完成了。你的每一個介面都可以按照以上的方式,寫成介面類的方法,然後用代理把它傳遞給其他類。
那麼其他類怎麼接受通過代理傳遞過來的資料呢?
開啟ViewController,匯入“Net.h”檔案,在繼承聲明後新增實現<NetDelegate>代理,如下程式碼:
// ViewController.h // NetInterface #import <UIKit/UIKit.h> #import "Net.h" @interface ViewController : UIViewController <NetDelegate> @end
為了使用方便我添加了一個Net類的成員變數,KYNet:
@property Net *KYNet;
接下來我們要在M檔案中使用介面嘍~~~程式碼如下:
// // ViewController.m // NetInterface #import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _KYNet=[Net getInstance];//得到單例
_KYNet.delegate=self; //將KYNet的代理與ViewController連線
} //北京按鈕 - (IBAction)beijingTouched:(id)sender { [_KYNet getWeatherInfoWithCity:@"Beijing"]; } //上海按鈕 - (IBAction)shanghaiTouched:(id)sender { [_KYNet getWeatherInfoWithCity:@"Shanghai"]; } @end
如上程式碼,當我們按下按鈕,就會使用我們的介面類傳送請求
慢著~怎麼接收伺服器反饋資料?!
對了,下面我們通過實現Net的代理方法來接受處理資料,並更新到UILabel上,在M檔案實現,Delegate的兩個方法:
-(void)getWeatherInfoSuccessFeedback:(id)feedbackInfo{ //當伺服器返回成功資料後,下列程式碼被啟用 NSLog(@"%@",[feedbackInfo class]); NSDictionary *dic=feedbackInfo; NSArray *weather1=[dic objectForKey:@"weather"]; NSDictionary *main1=[dic objectForKey:@"main"]; NSDictionary *weather=[weather1 objectAtIndex:0]; NSString *temp=[NSString stringWithFormat:@"%@",[main1 objectForKey:@"temp"]]; NSString *weatherInfo=[NSString stringWithFormat:@"%@",[weather objectForKey:@"description"]]; _condition.text=weatherInfo; _tem.text=temp; } -(void)getWeatherInfoFailFeedback:(id)failInfo{ NSLog(@"%@",failInfo); }
完活~
Hit Run~~~
總結
手指頭敲酸了...寫部落格比寫程式碼累多啦TT。Ok總結一下。
本文的核心思想是把所有的網路請求封裝成一個類,向外部提供各個介面的請求方法,以便使用者傳送請求;而當伺服器返回反饋資料後,外部通過實現代理方法來獲得資料。這樣的架構的好處是非常靈活,低耦合,擴充套件簡單。實現的代理方法會在伺服器返回資料的是時候自動被呼叫,結合非同步的AFNetworking,開發者不用去擔心執行緒問題。這樣一來,程式主線的邏輯設計也會變得很簡單。用此構架封裝好的類,可以輕鬆的打包成SDK給別人使用。
謝謝大家,希望你們有所收穫,一篇文章花了我整整一天時間,如果對你有所幫助請幫忙點贊。如有問題,歡迎評論。若要轉載,請註明出處。