天天看點

IOS應用架構思考一(網絡層)

今天就先講講網絡層的需要思考的問題吧。

我們都知道在用戶端發送請求是需要成本的,那麼設計異步的請求就是首要的問題。我們知道cocoa提供了非常豐富和易于使用的異步api, 有nsoperationqueue, dispatch queue, nsthread等。那麼如何選擇呢,答案毫無疑問的必須是nsoperation。 不信你去看asihttprequest和afnetworking. 好像這個理由不夠充分是吧,而且很多人就是使用了這些架構,而不清楚它們到底為何優秀,那我就列舉下一些它們的優點吧

首先舉個反例吧:曾經有人問過我“為什麼要用afnetworking和asi這樣的架構呢,我直接dispatch到背景用類似datawithurl:拉資料這樣不是很簡單?”。 那麼這樣的使用案例有2個很緻命的缺陷:第一個就是無法cancel這個請求,第二個是占用了完整的一個線程。

請求可以cancel的需求的重要性不用說了吧,别說你沒用過。使用nsoperation可以讓你友善的設計cancel一個請求的方法。

說道線程可能很多人對發送請求的時候線程有個很大的誤解: 每個請求占用一個線程。其實不是的,無論同時并發多少個請求,afnetworking和asi都是隻有一個線程在等待的。不信請看afnetworking的實作:

從上面的代碼中我們可以看到,afnetworking在等待請求時其實隻有個一個thread, 然後在這個thread上啟動一個runloop監聽 <code>nsurlconnection</code> 的 <code>nsmachport</code> 類型源。 <code>start</code>裡面直接就跳到這個線程去執行了, 在加入<code>nsoperationqueue</code>時,頂多<code>start</code>方法執行的時候占用一個線程,然後真正的發送請求和等待都是在這個<code>networkrequestthread</code>裡面進行的。

補充一下,上面的描述可能引起誤會,已經有人問我這個疑問了。注意, 上面講的<code>networkrequestthread</code> 并不是最終通路網絡并拉取資料的線程。真正的下載下傳資料是<code>nsurlconnection</code>統一排程的,<code>networkrequestthread</code> 隻是啟用了一個runloop來監聽<code>nsurlconnection</code>的回調事件。

了解上面兩點後我們再看前面的例子,就會發現問題有多緻命了。

如果同時并發了很多個請求,那就是實實在在的占用了n個線程了,而且請求不能cancel,對于這些被占用掉得大量資源就束手無策了。

如果用<code>nsoperationqueue</code>來說明,就是<code>maxconcurrentoperationcount</code>, 最大并發數量。

可能有人指出了, 剛才不是講請求都是在一個線程等待的嗎,那并發數量豈不是沒有太大意義? no, 并發數量還是有重要的意義的,它的主要意義就是控制連接配接數。&gt; 這裡多謝 casataloyum 同學的勘誤,我原本不知道2g, 3g, 等網絡協定的限制。

2g網絡下一次隻能維持一個連結,3g是2個,4g和wifi是不限。這個是對應協定的限制。如果超過這個限制發出的請求,就會報逾時。

除掉這個連接配接數的影響外,還有次要的影響就是帶寬,下載下傳資料是要流量的,如果同時并發了太多請求,每個連接配接都占用帶寬,可能導緻每個請求的時間均會延長,就好像你在迅雷同時下載下傳10個檔案,那麼下載下傳速度都慢了。不過考慮到現在的網速,這個場景一般都比較極限,在做一些特殊場景優化的時候或許可以考慮到。

當然<code>maxconcurrentoperationcount</code>也不能設定太小,太小了的話,如果個别的請求太慢,導緻後面的任務就啟動不起來了。

并發數量的考慮應該從上面兩個點出發,是以如果有人再扯到線程上去,隻能說走遠了。

可暫停的,可添加依賴的,這些可能不常會用到,但是如果要設計網絡層架構還是要考慮的,這也是要用<code>nsoperation</code>的原因之一。

本節内容說出了開源架構在operation的設計上的一些優點,是以也推薦這部分直接使用afnetworking之類的架構,因為優秀的開源元件總有其優點,他們考慮了很多你甚至都沒有意識到的問題,以前有是遇到過一些同學質疑afnetworking,質疑arc, 質疑sdwebimage, 質疑masonry等,當然不是說沒有缺點,但是這些東西當你真正深入了解學習後我覺得才能做出正确的選擇。

很多ios用戶端開發者不注重安全性,當然也因為ios系統已經做得很不錯的原因,還有安全的主要工作是在後端,比如使用https啊,比如加簽啊,那麼前段的網絡架構設計要注意哪些安全性的問題呢。

如無特殊情況,資料加密就推薦用https就夠了,好處就不一一列舉了,現在各大網際網路公司(如bat)都在做“全站https”. 而且現在免費的ssl證書也非常好申請,幾乎不要什麼成本了。

要保證接口參數不被篡改,不會被第三方惡意攻擊,就要對參數進行簽名。記得以前在某國内知名電商,由于接口沒有類似防護,一兩個小時被人利用移動端的注冊接口注冊了80w+賬号,慘不忍睹。 而且簽名密鑰(其實不是密鑰,就是個幹擾串)不要寫死,要動态下發。

無論你是用afnetworking之類的開源元件還是自己封裝的方式,一般我們都會封裝下中間元件,一來可以降低業務代碼和底層元件的耦合,二來友善拓展。那麼設計這些元件的時候需要注意哪些呢?

這部分選擇不少,主要是能統一和友善,可能會考慮下面2個問題 1、是選擇block回調方式,還是delegate, 還是target-action, 可以根據自己的喜好來設定,也可以相容多種。 2、success和fail分開回調還是同一個方法回調。

request的部分可能會有些場景需要請求前和請求後的攔截器的設計, 即aop入口,在這樣的攔截器接口中,我們就可以做一些事情如: 1、session過期後自動登入 2、需要登入的接口進入登入

在一個頁面中發送請求的時候,無疑會碰到一個問題,那就是“頁面退出時請求要取消”。

我們最開始是如何做的呢,就像記憶體管理,誰啟動,誰cancel, 即是單獨cancel. 使用方式類似下面:

然後就覺得每個地方都要在dealloc裡面寫cancel,很麻煩,于是可能想要做個統一的cancel,類似封裝個方法在父類裡面調用:

這樣的話好像就更友善了,代碼量也少了一些,調用的類也不用去管cancel了, 但是這樣真的好麼,雖然可能讓你暫時友善了,但是這樣的設計還是不緊湊,其實這個請求是否應該被取消應該是調用者的邏輯,這樣就相當于一部分的邏輯放在父類裡面,是有點坑的,第一種寫法雖然可能麻煩寫,但是就像記憶體管理,誰建立,誰負責銷毀,這樣的代碼可讀性和維護性更高些。不然如果遇到特殊的邏輯,可能要去動父類或底層。

說到這裡就是建議上面代碼中的request對象的封裝就是operation本身,這樣其實就不需要再在viewcontroller裡面寫很多isloading, isloaded的狀态位了啊,nsoperation自帶的。

說道網絡請求,就不能不提網絡緩存,資料緩存是提升使用者體驗不可缺少的重要一環。而對于緩存可以講的太多了,比如圖檔緩存可以單獨開篇文章來講了,這裡就講一下關于普通接口資料的緩存。一般我們對于緩存的方式有兩種做法,一種是用戶端寫緩存實作和緩存邏輯,一種是遵循http協定的緩存。

一般需要這種緩存的邏輯無非是考慮下面的需求:

1、接口傳回的資料很少變動,不希望做重複請求 2、網絡慢或者伺服器等異常狀況容災。

自己實作也比較靈活,你可以寫個資料庫來緩存、也可以直接序列化存檔案。但是缺點也比較明顯,那就是緩存時間不好定義。

既然做了緩存,那麼就會遇到這個問題,就是資料重新整理了之後,緩存不更新怎麼辦。那麼其實在你選擇這種緩存實作之前就需要做權衡,是資料實時性重要,還是資料加載速度重要。想好這個問題才能确定是否要這樣的緩存。

這種緩存方案是比較推薦的方案,http協定已經定義了好了一套緩存方案,為什麼不用呢。 而且蘋果已經幫我們實作好了緩存的代碼,nsurlcache,資料緩存在本地sqlite裡。不過就是要和服務端同學一塊推進。

可以緩存的接口,可以在傳回的 responseheaders 中添加 cache-control 和 expires 來告訴前端是否可以緩存和緩存時間等。

而用戶端可以通過設定 cachepolicy 來決定是否使用緩存

考慮一種場景,你不能确定資料什麼時候更新,可能随時重新整理,也可能幾天才更新,那麼我們如何使用緩存呢。 可以使用 <code>etag/if-none-match</code> 或者 <code>last-modified/if-modified-since</code>。

兩種方式原理差不多,就是在發送請求的 requestheaders 中加入 if-none-match 或者 if-modified-since 字段,和服務端資料進行比較是否有更新,有更新則傳回body,沒有的話就在responseheaders中告知使用緩存。 兩者中, etag是比較hash, last-modified是比較最後更改時間。

asihttprequest實作了這種模式,如下:

對于很多需要精細化體驗的app來說,http協定的模式已經不能夠滿足需求了, 比如支付寶可以随時随地的推活動。那麼就需要伺服器推的技術了。

spdy 和 http2 協定都定義了關于服務端推送的協定,還有一些其他的相對于http的優良特性. 一些大公司應該都做了對于spdy的支援,ios中可以使用twitter開源的cocoaspdy。 不過google剛剛宣布了不再支援spdy,以後都走http/2了。

關于這些我還沒有詳細的學習,這裡就不多講了,以後多多學習。

另一種比較靈活和強大的方法伺服器推送的方案就是tcp長連接配接了,長連接配接可以讓你做很多事情,唯一的難題就是要處理好心跳包。

在做國際化的時候,需要向伺服器提供使用者的語言偏好,也就是需要設定 <code>accept-language</code> 字段,如下:

即使你的伺服器還不支援國際化,把這個屬性放進去會讓你在需要的時候用到,而不必更新用戶端。<code>afnetworking</code>已經實作了改邏輯。

就寫到這裡吧,後面想到了再補充,歡迎讨論和指正。

繼續閱讀