大部分應用程式都或多或少會牽扯到網絡開發,例如說新浪微網誌、微信等,這些應用本身可能采用ios開發,但是所有的資料支撐都是基于背景網絡伺服器的。如今,網絡程式設計越來越普遍,孤立的應用通常是沒有生命力的。今天就會給大家介紹這部分内容:
<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#requestandresponse">web請求和響應</a>
使用代理方法
簡化請求方法
圖檔緩存
擴充--檔案分段下載下傳
擴充--檔案上傳
<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#nsurlsession">nsurlsession</a>
資料請求
檔案上傳
檔案下載下傳
會話
<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#uiwebview">uiwebview</a>
浏覽器實作
uiwebview與頁面互動
<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#networkstatus">網絡狀态</a>
目 錄
做過web開發的朋友應該很清楚,http是無連接配接的請求。每個請求request伺服器都有一個對應的響應response,無論是asp.net、jsp、php都是基于這種機制開發的。

在web開發中主要的請求方法有如下幾種:
get請求:get是擷取資料的意思,資料以明文在url中傳遞,受限于url長度,是以傳輸資料量比較小。
post請求:post是向伺服器送出資料的意思,送出的資料以實際内容形式存放到消息頭中進行傳遞,無法在浏覽器url中檢視到,大小沒有限制。
head請求:請求頭資訊,并不傳回請求資料體,而隻傳回請求頭資訊,常用用于在檔案下載下傳中取得檔案大小、類型等資訊。
程式的實作需要借助幾個對象:
nsurlrequest:建立了一個請求,可以指定緩存政策、逾時時間。和nsurlrequest對應的還有一個nsmutableurlrequest,如果請求定義為nsmutableurlrequest則可以指定請求方法(get或post)等資訊。
nsurlconnection:用于發送請求,可以指定請求和代理。目前調用nsurlconnection的start方法後開始發送異步請求。
程式代碼如下:
運作效果:
需要注意:
根據響應資料大小不同可能會多次執行- (void)connection:(nsurlconnection *)connection didreceivedata:(nsdata *)data方法。
url中不能出現中文(例如上面使用get傳參數時,file參數就可能是中文),需要對url進行編碼,否則會出錯。
當然,對于上面檔案下載下傳這種大資料響應的情況使用代理方法處理響應具有一定的優勢(可以獲得傳輸進度)。但是如果現響應資料不是檔案而是一段字元串(注意web請求的資料可以是字元串或者二進制,上面檔案下載下傳示例中響應資料是二進制),那麼采用代理方法處理伺服器響應就未免有些太麻煩了。其實蘋果官方已經提供了下面兩種方法處理一般的請求:
+ (void)sendasynchronousrequest:request: queue:queue:completionhandler:發送一個異步請求
+ (nsdata *)sendsynchronousrequest: returningresponse: error:發送一個同步請求
請求過程中需要傳遞一個使用者名和密碼,如果全部正确則伺服器端傳回此使用者可以看到的最新微網誌資料,響應的json格式大緻如下:
整個json最外層是statuses節點,它是一個數組類型,數組中每個元素都是一條微網誌資料,每條微網誌資料中除了包含微網誌資訊還包含了發表使用者的資訊。
首先需要先定義使用者模型kcuser
微網誌模型kcstatus
kcstatus.h
kcstatus.m
其次需要自定義微網誌顯示的單元格kcstatustableviewcell,這裡需要注意,由于伺服器傳回資料中頭像和會員類型圖檔已經不在本地,需要從伺服器端根據傳回json的中圖檔的路徑去加載。
kcstatustableviewcell.h
kcstatustableviewcell.m
最後就是kcmainviewcontroller,在這裡需要使用nsurlconnection的靜态方法發送請求、獲得請求資料,然後對請求資料進行json序列化,将json字元串序列化成微網誌對象通過uitableview顯示到界面中。
可以看到使用nsurlconnection封裝的靜态方法可以直接獲得nsdata,不需要使用代理一步步自己組裝資料。這裡采用了post方式發送請求,使用post發送請求需要組裝資料體,不過資料長度不像get方式存在限制。從ios5開始蘋果官方提供了json序列化和反序列化相關方法(上面程式中僅僅用到了反序列化方法,序列化使用datawithjsonobject:options:opt error:方法)友善的對數組和字典進行序列化和反序列化。但是注意反序列化參數設定,程式中設定成了0,直接反序列化為不可變對象以提高性能。
注意: 1.現在多數情況下網際網路資料都是以json格式進行傳輸,但是有時候也會面對xml存儲。在ios中可以使用nsxmlparser進行xml解析,由于實際使用并不多,在此不再贅述。 2.使用kvc給對象指派時(通常是nsdictionary或nsmutalbedictionary)注意對象的屬性最好不要定義為基本類型(如int),否則如果屬性值為null則會報錯,最後定義為objc對象類型(如使用nsnumber代替int等);
開發web類的應用圖檔緩存問題不得不提及,因為圖檔的下載下傳相當耗時。對于前面的微網誌資料,頭像和微網誌類型圖示在資料庫中是以連結形式存放的,取得連結後還必須進行對應的圖檔加載。大家都知道圖檔往往要比文本内容大得多,在uitableview中上下滾動就會重新加載資料,對于文本由于已經加載到本地自然不存在問題,但是對于圖檔來說如果每次都必須重新從伺服器端加載就不太合适了。
解決圖檔加載的辦法有很多,可以事先存儲到記憶體中,也可以儲存到臨時檔案。在記憶體中存儲雖然簡單但是往往不可取,因為程式重新啟動之後還面臨這重新請求的問題,類似于新浪微網誌、qq、微信等應用一般會存儲在檔案中,這樣應用程式即使重新開機也會從檔案中讀取。但是使用檔案緩存圖檔可能就要自己做很多事情,例如緩存檔案是否過期?緩存資料越來越大如何管理存儲空間?
這些問題其實很多第三方架構已經做的很好了,實際開發中往往會采用一些第三方架構來處理圖檔。例如這裡可以選用sdwebimage架構。sdwebimage使用起來相當簡單,開發者不必過多關心它的緩存和多線程加載問題,一個方法就可以解決。這裡直接修改kcstatustableviewcell中相關代碼即可:
在上面的方法中直接調用了sdwebimage的分類緩存方法設定圖檔,這個方法可以配置設定另外一個線程去加載圖檔(同時對于頭像還指定了預設圖檔,網速較慢時不至于顯示空白),圖檔加載後存放在沙箱的緩存檔案夾,如下圖:
通過前面的示範大家應該對于ios的web請求有了大緻的了解,可以通過代理方法接收資料也可以直接通過靜态方法接收資料,但是實際開發中更推薦使用靜态方法。關于前面的檔案下載下傳示例,更多的是希望大家了解代理方法接收響應資料的過程,實際開發中也不可能使用這種方法進行檔案下載下傳。這種下載下傳有個緻命的問題:不适合進行大檔案分段下載下傳。因為代理方法在接收資料時雖然表面看起來是每次讀取一部分響應資料,事實上它隻有一次請求并且也隻接收了一次伺服器響應,隻是當響應資料較大時系統會重複調用資料接收方法,每次将已讀取的資料拿出一部分交給資料接收方法。這樣一來對于上g的檔案進行下載下傳,如果中途暫停的話再次請求還是從頭開始下載下傳,不适合大檔案斷點續傳(另外說明一點,上面nsurlconnection示例中使用了nsmutabledata進行資料接收和追加隻是為了友善示範,實際開發建議直接寫入檔案)。
實際開發檔案下載下傳的時候不管是通過代理方法還是靜态方法執行請求和響應,我們都會分批請求資料,而不是一次性請求資料。假設一個檔案有1g,那麼隻要每次請求1m的資料,請求1024次也就下載下傳完了。那麼如何讓伺服器每次隻傳回1m的資料呢?
在網絡開發中可以在請求的頭檔案中設定一個range資訊,它代表請求資料的大小。通過這個字段配合伺服器端可以精确的控制每次伺服器響應的資料範圍。例如指定bytes=0-1023,然後在伺服器端解析range資訊,傳回該檔案的0到1023之間的資料的資料即可(共1024byte)。這樣,隻要在每次發送請求控制這個頭檔案資訊就可以做到分批請求。
當然,為了讓整個資料保持完整,每次請求的資料都需要逐漸追加直到整個檔案請求完成。但是如何知道整個檔案的大小?其實在前面的檔案下載下傳示範中大家可以看到,可以通過頭檔案資訊擷取整個檔案大小。但是這麼做的話就必須請求整個資料,這樣分段下載下傳就沒有任何意義了。所幸在web開發中我們還有另一種請求方法“head”,通過這種請求伺服器隻會響應頭資訊,其他資料不會傳回給用戶端,這樣一來整個資料的大小也就可以得到了。下面給出完整的程式代碼,關鍵的地方已經給出注釋(為了簡化代碼,這裡沒有使用代理方法):
kcmainviewcontroller.m
下載下傳檔案的生成過程:
在做web應用程式開發時,如果要上傳一個檔案往往會給form設定一個enctype=”multipart/form-data”的屬性,不設定這個值在背景無法正常接收檔案。在web開發過程中,form的這個屬性其實本質就是指定請求頭中content-type類型,當然使用get方法送出就不用說了,必須使用url編碼。但是如果使用post方法傳遞資料其實也是類似的,同樣需要進行編碼,具體編碼方式其實就是通過enctype屬性進行設定的。常用的屬性值有:
application/x-www-form-urlencoded:預設值,發送前對所有發送資料進行url編碼,支援浏覽器通路,通常文本内容送出常用這種方式。
multipart/form-data:多部分表單資料,支援浏覽器通路,不進行任何編碼,通常用于檔案傳輸(此時傳遞的是二進制資料) 。
text/plain:普通文本資料類型,支援浏覽器通路,發送前其中的空格替換為“+”,但是不對特殊字元編碼。
application/json:json資料類型,浏覽器通路不支援 。
text/xml:xml資料類型,浏覽器通路不支援。
要實作檔案上傳,必須采用post上傳,同時請求類型必須是multipart/form-data。在web開發中,開發人員不必過多的考慮mutiparty/form-data更多的細節,一般使用file控件即可完成檔案上傳。但是在ios中如果要實作檔案上傳,就沒有那麼簡單了,我們必須了解這種資料類型的請求是如何工作的。
下面是在浏覽器中上傳一個檔案時,發送的請求頭:
這是發送的請求體内容:
在請求頭中,最重要的就是content-type,它的值分為兩部分:前半部分是内容類型,前面已經解釋過了;後面是邊界boundary用來分隔表單中不同部分的資料,後面一串數字是浏覽器自動生成的,它的格式并不固定,可以是任意字元。和請求體中的源代碼部分進行對比不難發現其實boundary的内容和請求體的資料部分前的字元串相比少了兩個“--”。請求體中content-disposition中指定了表單元素的name屬性和檔案名稱,同時指定了content-type表示檔案類型。當然,在請求體中最重要的就是後面的資料部分,它其實就是二進制字元串。由此可以得出以下結論,請求體内容由如下幾部分按順序執行組成:
了解這些資訊後,隻要使用post方法給伺服器端發送請求并且請求内容按照上面的格式設定即可。
下面是實作代碼:
nsurlconnection是2003年伴随着safari一起發行的網絡開發api,距今已經有十一年。當然,在這十一年間它表現的相當優秀,有大量的應用基礎,這也是為什麼前面花了那麼長時間對它進行詳細介紹的原因。但是這些年伴随着iphone、ipad的發展,對于nsurlconnection設計理念也提出了新的挑戰。在2013年wwdc上蘋果揭開了nsurlsession的面紗,将它作為nsurlconnection的繼任者。相比較nsurlconnection,nsurlsession提供了配置會話緩存、協定、cookie和證書能力,這使得網絡架構和應用程式可以獨立工作、互不幹擾。另外,nsurlsession另一個重要的部分是會話任務,它負責加載資料,在用戶端和伺服器端進行檔案的上傳下載下傳。
通過前面的介紹大家可以看到,nsurlconnection完成的三個主要任務:擷取資料(通常是json、xml等)、檔案上傳、檔案下載下傳。其實在nsurlsession時代,他們分别由三個任務來完成:nsurlsessiondata、nsurlsessionuploadtask、nsurlsessiondownloadtask,這三個類都是nsurlsessiontask這個抽象類的子類,相比直接使用nsurlconnection,nsurlsessiontask支援任務的暫停、取消和恢複,并且預設任務運作在其他非主線程中,具體關系圖如下:
前面通過請求一個微網誌資料進行資料請求示範,現在通過nsurlsessiondatatask實作這個功能,其實作流程與使用nsurlconnection的靜态方法類似,下面是主要代碼:
下面看一下如何使用nsurlsessionuploadtask實作檔案上傳,這裡貼出主要的幾個方法:
如果僅僅通過上面的方法或許檔案上傳還看不出和nsurlconnection之間的差別,因為拼接上傳資料的過程和前面是一樣的。事實上在nsurlsessionuploadtask中還提供了一個- (nsurlsessionuploadtask *)uploadtaskwithrequest:(nsurlrequest *)request fromfile:(nsurl *)fileurl completionhandler:(void (^)(nsdata *data, nsurlresponse *response, nserror *error))completionhandler方法用于檔案上傳。這個方法通常會配合“put”請求進行使用,由于put方法包含在web dav協定中,不同的web伺服器其配置啟用put的方法也不同,并且出于安全考慮,各類web伺服器預設對put請求也是拒絕的,是以實際使用時還需做重分考慮,在這裡不具體介紹,有興趣的朋友可以自己試驗一下。
使用nsurlsessiondownloadtask下載下傳檔案的過程與前面差不多,需要注意的是檔案下載下傳檔案之後會自動儲存到一個臨時目錄,需要開發人員自己将此檔案重新放到其他指定的目錄中。
nsurlconnection通過全局狀态來管理cookies、認證資訊等公共資源,這樣如果遇到兩個連接配接需要使用不同的資源配置情況時就無法解決了,但是這個問題在nsurlsession中得到了解決。nsurlsession同時對應着多個連接配接,會話通過工廠方法來建立,同一個會話中使用相同的狀态資訊。nsurlsession支援程序三種會話:
<code>defaultsessionconfiguration</code>:程序内會話(預設會話),用硬碟來緩存資料。
<code>ephemeralsessionconfiguration</code>:臨時的程序内會話(記憶體),不會将cookie、緩存儲存到本地,隻會放到記憶體中,當應用程式退出後資料也會消失。
<code>backgroundsessionconfiguration</code>:背景會話,相比預設會話,該會話會在背景開啟一個線程進行網絡資料處理。
下面将通過一個檔案下載下傳功能對兩種會話進行示範,在這個過程中也會用到任務的代理方法對上傳操作進行更加細緻的控制。下面先看一下使用預設會話下載下傳檔案,代碼中示範了如何通過nsurlsessionconfiguration進行會話配置,如果通過代理方法進行檔案下載下傳進度展示(類似于前面中使用nsurlconnection代理方法,其實下載下傳并未分段,如果需要分段需要配合背景進行),同時在這個過程中可以準确控制任務的取消、挂起和恢複。
示範效果:
nsurlsession支援程式的背景下載下傳和上傳,蘋果官方将其稱為程序之外的上傳和下載下傳,這些任務都是交給背景守護線程完成的,而非應用程式本身。即使檔案在下載下傳和上傳過程中崩潰了也可以繼續運作(注意如果使用者強制退關閉應用程式,nsurlsession會斷開連接配接)。下面看一下如何在背景進行檔案下載下傳,這在實際開發中往往很有效,例如在手機上緩存一個視訊在沒有網絡的時候觀看(為了簡化程式這裡不再示範任務的取消、挂起等操作)。下面對前面的程式稍作調整使程式能在背景完成下載下傳操作:
當nsurlsession在背景開啟幾個任務之後,如果有其中幾個任務完成後系統會調用此應用程式的-(void)application:(uiapplication *)application handleeventsforbackgroundurlsession:(nsstring *)identifier completionhandler:(void (^)())completionhandler代理方法;此方法會包含一個competionhandler(此操作表示應用完成所有處理工作),通常我們會儲存此對象;直到最後一個任務完成,此時會重新通過會話辨別(上面sessionconfig中設定的)找到對應的會話并調用nsurlsession的-(void)urlsessiondidfinisheventsforbackgroundurlsession:(nsurlsession *)session代理方法,在這個方法中通常可以進行ui更新,并調用completionhandler通知系統已經完成所有操作。具體兩個方法代碼示例如下:
網絡開發中還有一個常用的ui控件uiwebview,它是ios中内置的浏覽器控件,功能十分強大。如一些社交軟體往往在應用程式内不需要打開其他浏覽器就能看一些新聞之類的頁面,就是通過這個控件實作的。需要注意的是uiwebview不僅能加載網絡資源還可以加載本地資源,目前支援的常用的文檔格式如:html、pdf、docx、txt等。
下面将通過一個uiwebview開發一個簡單的浏覽器,界面布局大緻如下:
在這個浏覽器中将實作這樣幾個功能:
1.如果輸入以”file://”開頭的位址将加載bundle中的檔案
2.如果輸入以“http”開頭的位址将加載網絡資源
3.如果輸入内容不符合上面兩種情況将使用bing搜尋此内容
其實uiwebview整個使用相當簡單:建立url->建立請求->加載請求,無論是加載本地檔案還是web内容都是這三個步驟。uiwebview内容加載事件同樣是通過代理通知外界,常用的代理方法如開始加載、加載完成、加載出錯等,這些方法通常可以幫助開發者更好的控制請求加載過程。
注意:uiwebview打開本地pdf、word檔案依靠的并不是uiwebview自身解析,而是依靠mime type識别檔案類型并調用對應應用打開。
uiwebview與頁面的互動主要展現在兩方面:使用objc方法進行頁面操作、在頁面中調用objc方法兩部分。和其他移動作業系統不同,ios中所有的互動都集中于一個stringbyevaluatingjavascriptfromstring:方法中,以此來簡化開發過程。
1.首先在request方法中使用loadhtmlstring:加載了html内容,當然你也可以将html放到bundle或沙盒中讀取并且加載。
2.然後在webviewdidfinishload:代理方法中通過stringbyevaluatingjavascriptfromstring: 方法可以操作頁面中的元素,例如在下面的方法中讀取了頁面标題、修改了其中的内容。
頁面中的js是無法直接調用objc方法的,但是可以變換一下思路:當需要進行一個js操作時讓頁面進行一個重定向,并且在重定向過程中傳入一系列參數。在uiwebview的代理方法中有一個webview: shouldstartloadwithrequest:navigationtype方法,這個方法會在頁面加載前執行,這樣可以在這裡攔截重定向,并且擷取定向url中的參數,根據這些參數約定一個方法去執行。
當通路百度搜尋手機版時會發現,有時候點選頁面中的某個元素可以調出ios作業系統的uiactionsheet,下面不妨模拟一下這個過程。首先需要定義一個js方法,為了友善擴充,這個js儲存在myjs.js檔案中存放到bundle中,同時在頁面中加載這個檔案内容。myjs.js内容如下:
這個js的功能相當單一,調用showsheet方法則會進行一個重定向,調用過程中需要傳遞一系列參數,當然這些參數都是uiactionsheet中需要使用的,注意這裡約定所有調用uiactionsheet的方法參數都以”kcactionsheet”開頭。
然後在webview: shouldstartloadwithrequest:navigationtype方法中截獲以“kcactionsheet”協定開頭的請求,對于這類請求獲得對應參數調用uiactionsheet。看一下完整代碼:
前面無論是下載下傳還是上傳都沒有考慮網絡狀态,事實上實際開發過程中這個問題是不得不思考的,試想目前誰會用3g或4g網絡下載下傳一個超大的檔案啊,是以實際開發過程中如果程式部署到了真機上必須根據不同的網絡狀态決定使用者的操作,例如下圖就是在使用qq音樂播放線上音樂的提示:
<a href="http://pan.baidu.com/s/1hqkwt3u" target="_blank"></a>