以前安居客iOS app的第二版架構大部份內容是我作的,期間有總結了一些經驗。在將近一年以後,前同事zzz在微信朋友圈上發了一個問題:假如問你一個iOS or Android app的架構,你會從哪些方面來講呢?
數據庫
當時看到這個問題正好在乘公車回家的路上,閒來無聊就答了一把。在zzz在微信朋友圈上追問了幾個問題以後,我以爲有必要開個博客專門來說講一些我的看法。編程
其實對於iOS客戶端應用的架構來講,複雜度不亞於服務端,但側重點和入手點卻跟服務端不太同樣。好比客戶端應用就不須要考慮相似C10K的問題,正常的app就根本不須要考慮。緩存
這系列文章我會主要專一在iOS應用架構方面,不少方案也是基於iOS技術棧的特色而創建的。由於我我的不是很喜歡寫Java,因此Android 這邊的我就不太瞭解了。若是你是Android開發者,你能夠側重看我提出的一些架構思想,畢竟無論作什麼,思路是相通的,實現手段不一樣罷了。安全
其實市面上大部分應用不外乎就是顛過來倒過去地作如下這些事情:性能優化
--------------- --------------- --------------- --------------- | | | | | | | | | 調用網絡API | --> | 展示列表 | --> | 選擇列表 | --> | 展示單頁 | | | | | | | | | --------------- --------------- --------------- --------------- ^ | | | | | ------------------------------------------
簡單來講就是調API,展現頁面,而後跳轉到別的地方再調API,再展現頁面。服務器
非也,非也。 ---- 包不一樣 《天龍八部》微信
App確實就是主要作這些事情,可是支撐這些事情的基礎,就是作架構要考慮的事情。網絡
上面這四大點,稍微細說一下就是:架構
上面幾點是針對App說的,下面還有一些是針對團隊說的:app
一時半會兒我仍是隻能想到上面這三點,事實上應該還會有不少,想不起來了。
因此當咱們討論客戶端應用架構的時候,咱們討論的差很少就是這些問題。
這系列文章主要是回答如下這些問題:
上面細分出來的四個問題,我會分別在四篇文章裏面寫。那麼這篇文章就是來說一些通識啥的,也是開個坑給你們討論通識問題的。
全部事情最難的時候都是開始作的時候,當你開始着手設計並實現某一層的架構乃至整個app的架構的時候,頗有可能會出現暫時的無從下手的狀況。如下 方法論是我這些年總結出來的經驗,每一個架構師也必定都有一套本身的方法論,但同樣的是,無論你採用什麼方法,全局觀、高度的代碼審美能力、靈活使用各類設 計模式必定都是貫穿其中的。歡迎各位在評論區討論。
第一步:搞清楚要解決哪些問題,並找到解決這些問題的充要條件
你必須得清楚你要作什麼,業務方但願要什麼。而不是爲了架構而架構,也不是爲了體驗新技術而改架構方案。之前是MVC,最近流行MVVM,若是過去的MVC是個好架構,沒什麼特別大的缺陷,就不要推倒而後搞成MVVM。
關於充要條件我也要說明一下,有的時候系統提供的函數是須要額外參數的,好比read函數。還有翻頁的時候,當前頁碼也是充要條件。但對於業務方來講,這些充要條件還可以再縮減。
好比read,須要給出file descriptor,須要給出buf,須要給出size。可是對於業務方來講,充要條件就只要file descriptor就夠了。再好比翻頁,其實業務方並不須要記錄當前頁號,你給他暴露一個loadNextPage
這樣的方法就夠了。
搞清楚對於業務方而言的真正充要條件很重要!這決定了你的架構是否足夠易用。另外,傳的參數越少,耦合度相對而言就越小,你替換模塊或者升級模塊所花的的代價就越小。
第二步:問題分類,分模塊
這個不用多說了吧。
第三步:搞清楚各問題之間的依賴關係,創建好模塊交流規範並設計模塊
關鍵在於創建一套統一的交流規範。這一步很可以體現架構師在軟件方面的價值觀,雖然存在必定程度上的好壞優劣(好比胖Model和瘦Model), 但既然都是架構師了,基本上是不會設計出明顯很爛的方案的,除非這架構師還未入流。因此這裏是架構師價值觀輸出的一個窗口,從這一點咱們是可以看出架構師 的素質的。
另外要注意的是,必定是創建一套統一的交流規範,不是兩套,不是多套。你要堅持你的價值觀,不要搖擺不定。要是搞出各類五花八門的規範出來,一方面有不切實際的炫技嫌疑,另外一方面也會帶來後續維護的災難。
第四步:推演預測一下將來可能的走向,必要時添加新的模塊,記錄更多的基礎數據以備將來之需
不少稱職的架構師都會在這時候考慮架構將來的走向,以及考慮作完這一輪架構以後,接下來要作的事情。一個好的架構雖然是功在當代利在千秋的工程,但絕對不是一個一勞永逸的工程。軟件是有生命的,你作出來的架構決定了這個軟件它這一輩子是坎坷仍是幸福。
第五步:先解決依賴關係中最基礎的問題,實現基礎模塊,而後再用基礎模塊堆疊出整個架構
這一步也是驗證你以前的設計是否合理的一步,隨着這一步的推動,你頗有可能會遇到須要對架構進行調整的狀況。這個階段必定要吹毛求疵高度負責地去開發,不要得過且過,發現架構有問題就及時調整。不然之後調整的成本就很是之大了。
第六步:打點,跑單元測試,跑性能測試,根據數據去優化對應的地方
你得用這些數據去向你的boss邀功,你也得用這些數據去不斷調整你的架構。
總而言之就是要遵循這些原則:自頂向下設計
(1,2,3,4步),自底向上實現
(5),先測量,後優化
(6)。
作不到這點,你就是碼農
作不到這點,你就是運維
作不到這點,你比較適合去競爭對手那兒當工程師
作不到這點,公關行業比較適合你
作不到這點,你就是高中編程愛好者
作不到這點,(我想了很久,但我仍是不知道你適合去幹什麼。)
以上是我判斷一個架構是否是好架構的標準,這是根據重要性來排列的。客戶端架構跟服務端架構要考慮的問題和側重點是有一些區別的。下面我會針對每一點詳細講解一下:
代碼整齊,分類明確,沒有common,沒有core
代碼整齊是每個工程師的基本素質,先不說你搞定這個問題的方案有多好,解決速度有多快,若是代碼不整齊,一切都白搭。由於你的代碼是要給別人看 的,你本身也要看。若是哪一天架構有修改,正好改到這個地方,你很容易本身都看不懂。另外,破窗理論提醒咱們,若是代碼不整齊分類不明確,整個架構會隨着 一次一次的拓展而愈來愈混亂。
分類明確的字面意思你們必定都瞭解,但還有一個另外的意思,那就是:不要讓一個類或者一個模塊作兩種不一樣的事情
。若是有類或某模塊作了兩種不一樣的事情,一方面不適合將來拓展,另外一方面也會形成分類困難。
不要搞Common,Core這些東西。每家公司的架構代碼庫裏面,最噁心的必定是這兩個名字命名的文件夾,我這麼說必定不會錯。不要開 Common,Core這樣的文件夾,開了以後後來者必定會把這個地方搞得一團糟,最終變成Common也不Common,Core也不Core。要記 住,架構是不斷成長的,是會不斷變化的。不是每次成長每次變化,都是由你去實現的。若是真有什麼東西特別小,那就索性爲了他單獨開闢一個模塊就行了,小就 小點,關鍵是要有序。
不用文檔,或不多文檔,就能讓業務方上手
誰特麼會去看文檔啊,業務方他們已經被產品經理逼得很忙了。因此你要儘量讓你的API名字可讀性強,對於iOS來講,objc這門語言的特性把這個作到了極致,函數名長就長一點,沒關係。
好的函數名:
- (NSDictionary *)exifDataOfImage:(UIImage *)image atIndexPath:(NSIndexPath *)indexPath; 壞的函數名: - (id)exifData:(UIImage *)image position:(id)indexPath callback:(id<ErrorDelegate>)delegate; 爲何壞? 1. 不要直接返回id或者傳入id,實在不行,用id<protocol>也比id好。若是連這個都作不到,你要好好考慮你的架構是否是有問題。 2. 要告知業務方要傳的東西是什麼,好比要傳Image,那就寫上ofImage。若是要傳位置,那就要寫上IndexPath,而不是用position這麼籠統的東西 3. 沒有任何理由要把delegate做爲參數傳進去,必定不會有任何狀況不得不這麼作的。並且delegate這個參數根本不是這個函數要解決的問題的充要條件,若是你發現你不得不這麼作,那必定是架構有問題!
思路和方法要統一,儘可能不要多元
解決一個問題會有不少種方案,可是一旦肯定了一種方案,就不要在另外一個地方採用別的方案了。也就是作架構的時候,你得時刻記住當初你決定要處理這樣類型的問題的方案是什麼,以及你的初衷是什麼,不要搖擺不定。
另外,你當初設立這個模塊必定是有想法有緣由的,要記錄下你的解決思路,不要到時候換個地方你又靈光一現啥的,引入了其餘方案,從而致使異構。
要是一個框架裏面解決同一種相似的問題有各類五花八門的方法或者類,我以爲作這個架構的架構師必定是本身都沒想清楚就開始搞了。
沒有橫向依賴,萬不得已不出現跨層訪問
沒有橫向依賴是很重要的,這決定了你未來要對這個架構作修補所須要的成本有多大。要作到沒有橫向依賴,這是很考驗架構師的模塊分類能力和是否熟悉業務的。
跨層訪問是指數據流向了跟本身沒有對接關係的模塊。有的時候跨層訪問是不可避免的,好比網絡底層裏面信號從2G變成了3G變成了4G,這是有可能需 要跨層通知到View的。但這種狀況很少,一旦出現就要想盡一切辦法在本層搞定或者交給上層或者下層搞定,儘可能不要出現跨層的狀況。跨層訪問一樣也會增長 耦合度,當某一層須要總體替換的時候,牽涉面就會很大。
對業務方該限制的地方有限制,該靈活的地方要給業務方創造靈活實現的條件
把這點作好,很依賴於架構師的經驗。架構師必需要有能力區分哪些狀況須要限制靈活性,哪些狀況須要創造靈活性。好比對於Core Data技術棧來講,ManagedObject理論上是能夠出如今任何地方的,那就意味着任何地方均可以修改ManagedObject,這就致使 ManagedObjectContext在同步修改的時候把各類不一樣來源的修改同步進去。這時候就須要限制靈活性,只對外公開一個修改接口,不暴露任何 ManagedObject在外面。
若是是設計一個ABTest相關的API的時候,咱們又但願增長它的靈活性。使得業務方不光能夠經過Target-Action的模式實現ABtest,也要能夠經過Block的方式實現ABTest,要儘量知足靈活性,減小業務方的使用成本。
易測試易拓展
老生常談,要實現易測試易拓展,那就要提升模塊化程度,儘量減小依賴關係,便於mock。另外,若是是高度模塊化的架構,拓展起來將會是一件很是容易的事情。
保持必定量的超前性
這一點能看出架構師是否關注行業動態,是否能準確把握技術走向。保持適度的技術上的超前性,可以使得你的架構更新變得相對輕鬆。
另外,這裏的超前性也不光是技術上的,還有產品上的。誰說架構師就不須要跟產品經理打交道了,沒事多跟產品經理聊聊天,聽聽他對產品將來走向的暢 想,你就能夠在合理的地方爲他的暢想留一條路子。同時,在創業公司的環境下,不少產品需求其實只是爲了趕產品進度而產生的妥協方案,最後仍是會轉到正軌 的。這時候業務方能夠不實現轉到正規的方案,可是架構這邊,是必定要爲這種可預知的改變作準備的。
接口少,接口參數少
越少的接口越少的參數,就能越下降業務方的使用成本。固然,充要條件仍是要知足的,如何在知足充要條件的狀況下儘量地減小接口和參數數量,這就能看出架構師的功力有多深厚了。
高性能
爲何高性能排在最後一位?
高性能很是重要,可是在客戶端架構中,它不是第一考慮因素。緣由有下:
可是!不重要不表明用不着去作,關於性能優化的東西,我會對應放到各系列文章裏面去。好比網絡層優化,那就會在網絡層方案的那篇文章裏面去寫,對應每層架構都有每層架構的不一樣優化方案,我都會在各自文章裏面一一細說。
昨晚上志豪看了這篇文章以後說,看到你這個題目原本我是指望看到關於架構分層相關的東西的,可是你沒寫。
嗯,確實沒寫,當時沒寫的緣由是感受這個沒什麼好寫的。前面談論到架構的方法的時候,關於問題分類分模塊這一步時,架構分層也屬於這一部分,給我一筆帶過了。
既然志豪提出來了這個問題,我想可能你們關於這個也會有一些想法和問題,那麼我就在這兒講講吧。
其實分層這種東西,真沒啥技術含量,全憑架構師的經驗和素質。
咱們常見的分層架構,有三層架構的:展示層、業務層、數據層。也有四層架構的:展示層、業務層、網絡層、本地數據層。這裏說三層
、四層
,跟TCP/IP所謂的五層或者七層不是同一種概念。再具體說就是:你這個架構在邏輯上是幾層那就幾層,具體每一層叫什麼,作什麼,沒有特定的規範。這主要是針對模塊分類而言的。
也有說MVC架構,MVVM架構的,這種層次劃分,主要是針對數據流動的方向而言的。
在實際狀況中,針對數據流動方向作的設計和針對模塊分類作的設計是會放在一塊兒的,也就是說,一個MVC架構能夠是四層:展示層、業務層、網絡層、本地數據層。
那麼,爲何我要說這個?
大概在五六年前,業界很流行三層架構
這個術語。而後各類文檔資料漫天的三層架構
,而且喜歡把它與MVC
放在一塊兒說,MVC三層架構/三層架構MVC
,以致於不少人就會認爲三層架構
就是MVC
,MVC
就是三層架構
。其實不是的。三層架構
裏面其實沒有Controller
的概念,並且三層架構描述的側重點是模塊之間的邏輯關係。MVC
有Controller
的概念,它描述的側重點在於數據流動方向。
好,爲何流行起來的是
三層架構
,而不是四層架構
或五層架構
?
由於全部的模塊角色只會有三種:數據管理者
、數據加工者
、數據展現者
,意思也就是,籠統說來,軟件只會有三層,每一層扮演一個角色。其餘的第四層第五層,通常都是這三層裏面的其中之一分出來的,最後都能概括進這三層的某一層中去,因此用三層架構
來描述就比較廣泛。
那麼咱們怎麼作分層?
應該如何作分層,不是在作架構的時候一開始就考慮的問題。雖然咱們要按照自頂向下的設計方式來設計架構,可是通常狀況下不適合直接從三層開始。通常 都是先肯定全部要解決的問題,先肯定都有哪些模塊,而後再基於這些模塊再往下細化設計。而後再把這些列出來的問題和模塊作好分類。分類以後不出意外大多數 都是三層。若是發現某一層特別龐大,那就能夠再拆開來變成四層,變成五層。
舉個例子:你要設計一個即時通信的服務端架構,怎麼分層?
記住,不要一上來就把三層架構
的規範套上去,這樣作是作不出好架構的。
你要先肯定都須要解決哪些問題。這裏只是舉例子,我隨意列出一點意思意思就行了:
解決第一個問題須要一個連接管理模塊,連接管理模塊通常是經過連接池來實現。 解決第二個問題須要有一個數據交換模塊,從A接收來的數據要給到B,這個事情由這個模塊來作。 解決第三個問題須要有個數據庫,若是是服務於大量用戶,那麼就須要一個緩衝區,只有當須要存儲的數據達到必定量時才執行寫操做。 解決第四個問題能夠有幾種解決方案,一個是集羣中有那麼幾臺服務器做爲尋路服務器,全部尋路的服務交給那幾臺去作,那麼你須要開發一個尋路服務的 Daemon。或者用廣播方式尋路,但若是尋路頻次很是高,會形成集羣內部網絡負載特別大。這是你要權衡的地方,目前流行的思路是去中心化,那麼要解決網 絡負載的問題,你就能夠考慮配置一個緩存。
因而咱們有了這些模塊:
連接管理、數據交換、數據庫及其配套模塊、尋路模塊
作到這裏還遠遠沒有結束,你要繼續針對這四個模塊繼續往下細分,直到足夠小爲止。可是這裏只是舉例子,因此就不往下深究了。
另外,我要提醒你的是,直到這時,仍是跟幾層架構毫無關係的。當你把全部模塊都找出來以後,就要開始整理你的這些模塊,頗有可能架構圖就是這樣:
連接管理 收發數據 收發數據 數據交換 / \ \ 連接管理 數據交換 尋路服務 ========\ / \ ========/ 數據庫服務 尋路服務 數據庫服務 /
而後這些模塊分完以後你看一下圖,嗯,一、二、3,一共三層,因此那就是三層架構
啦。在這裏最消耗腦力最考驗架構師功力的地方就在於:找到全部須要的模塊
, 把模塊放在該放的地方
這個例子側重點在於如何分層,性能優化、數據交互規範和包協議、數據採集等其餘一系列必要的東西都沒有放進去,但看到這裏,相信你應該瞭解架構師是怎麼對待分層問題的了吧?
對的,答案就是沒有分層。所謂的分層都是出架構圖以後的事情了。因此你看別的架構師在演講的時候,上來第一句話差很少都是:"這個架構分爲如下幾層..."。但考慮分層的問題的時機絕對不是一開始就考慮的。另外,模塊必定要把它設計得獨立性強,這實際上是門藝術活。
另外,這雖然是服務端架構,可是思路跟客戶端架構是同樣的,側重點不一樣罷了。之因此不拿客戶端架構舉例子,是由於這方面的客戶端架構蘋果已經幫你作好了絕大部分事情,沒剩下什麼值得說的了。
評論區MatrixHero提到一點:
關於common文件夾的問題,僅僅是文件夾而已,別無他意。若是後期維護出了代碼混亂多是由於,和服務器溝通協議不統一,或代碼review不及時。應該有專人維護公共類。
這是針對我前面提出的不要Common,不要Core
而言的,爲何我建議你們不要開Common文件夾?我打算分幾種狀況給你們解釋一下。
通常狀況下,咱們都會有一些屬於這個項目的公共類,好比取定位座標,好比圖像處理。這些模塊可能很是小,就h和m兩個文件。單獨拎出來成爲一個模塊 感受未入流,可是又不屬於其餘任何一個模塊。因而你們頗有可能就會把它們放入Common裏面,我目前見到的大多數工程和大多數文檔裏面的代碼都喜歡這麼 作。在當時來看,這麼作看不出什麼問題,但關鍵在於:軟件是有生命,會成長的
。當時分出來的小模塊,頗有可能會隨着業務的成長,逐漸發展成大模塊,發展成大模塊後,能夠再把它從Common移出來單獨成立一個模塊。這個在理論上是沒有任何問題的,然而在實際操做過程當中,工程師在拓張這個小模塊的時候,不太容易會去考慮橫向依賴
的問題,由於當時這些模塊都在Common裏面,直接進行互相依賴是很是符合直覺的,並且也不算是不遵照規範。然而要注意的是,這纔是Commom代碼混亂
的罪魁禍首,Common文件夾縱容了不精心管理依賴
的作法。當Common裏面的模塊依賴關係變得複雜,再想要移出來單獨成立一個模塊,就不是當初設置Common時想的等規模大了再移除也不遲
那麼簡單了。
另外,
Common
有的時候也不只僅是一個文件夾。
在使用Cocoapods來管理項目庫的時候,Common
每每就是一個pod。這個pod裏面會有A/B/C/D/E這些函數集或小模塊。若是要新開一個app或者Demo,勢必會使用到Common這個pod,這麼作,每每會把不須要包含的代碼也包含進去,我對項目有高度潔癖,這種狀況會讓我以爲很是不舒服。
舉個例子:早年安居客的app還不是集齊全部新房
、二手房
、租房
業務的。當你剛開始寫新房
這個app的時候,建立了一個Common這個pod,這裏麪包含了一些對於新房
來講比較Common的代碼,也包含了對於這個app來講比較Common的代碼。過了半年或者一年,你要開始二手房
這個app,我以爲大多數人都會選擇讓二手房
也包含這個Common,因而這個Common頗有可能本身走上另外一條發展的道路。等到了租房
這個業務要開app的時候,Common已經很是之龐大,相信這時候的你也不會去想整理Common的事情了,先把租房
搞定,因而Common最終就變成了一坨屎。
就對於上面的例子來講,還有一個要考慮的是,分出來的三個業務頗有可能會有三個Common,假設三個Common裏面都有公共的功能,交給了三個 團隊去打理,若是遇到某個子模塊須要升級,那麼三個Common裏面的這個子模塊都要去同步升級,這是個很不效率的事情。另外,頗有可能三個Common 到最後發展成彼此不兼容,可是代碼類似度很是之高,這個在架構上,是屬於分類條理不清
。
就在去年年中的時候,安居客決定將三個業務歸併到同一個App。好了,若是你是架構師,面對這三個Common,你打算怎麼辦?要想最快出成果,那 就只好忍受代碼冗餘,趕忙先把架子搭起來再說,不然你面對的就是剪不斷理還亂的Common。此時Common就已經很無奈地變成一坨屎了。這樣的 Common,你本身說不定也搞不清楚它裏面到底都有些什麼了,交給任何一我的去打理,他都不敢作完全的整理的。
還有就是,Common自己就是一個粒度很是大的模塊。在阿里這樣大規模的團隊中,即使新開一個業務,都須要在整個app的環境下開發,爲何?因 爲模塊拆分粒度不夠,要想開一個新業務,必須把其餘業務的代碼以及依賴所有拉下來,而後再開新入口,你的新業務才能進行正常的代碼編寫和調試。然而你的新 業務其實只依賴首頁入口、網絡庫等這幾個小模塊,不須要依賴其餘那麼多的跟你不要緊的業務。如今每次打開天貓的項目,我都要等個兩三分鐘,這很是之蛋疼。
可是你們真的不知道這個緣由嗎?知道了這個緣由,爲何沒人去把這些粒度不夠細的模塊整理好?在我看來,這件事沒人敢作。
花這麼大的成本只是爲了減小開啓項目時候等待IDE打開時的那幾分鐘時間?我想若是我是你leader,我也應該不會批准你作這樣的事情的。因此, 與其到了後面吃這個苦頭,不如一開始作架構的時候就不要設置Common,到後面就能省力不少。架構師的工做爲何是功在當代利在千秋,架構師的素質爲什 麼對團隊這麼重要?我以爲這裏就是一個最好的體現。
簡而言之,不建議開Common的緣由以下:
那麼,不設Common會帶來哪些好處?
Common的好處只有一個,就是前期特別省事兒。然而它的壞處比好處要多太多。不設置Common,再小的模塊再小的代碼也單獨拎出來,最多就是 Podfile裏面要多寫幾行,多寫幾行最多隻花費幾分鐘。但若要消除Common所帶來的罪孽,不是這幾分鐘就能搞定的事情。既然不用Common的好 處這麼多,那何樂而不爲呢?
假設未來你的項目中有一個類是用來作Location的,哪怕只有兩個文件,也給他開一個模塊就叫Location。若是你的項目中有一個類是用來 作ImageProcess的,那也開一個模塊就叫ImageProcess。不要都放到Common裏面去,未來你再開新的項目或者新的業務,用 Location就寫依賴Location,用ImageProcess就寫依賴ImageProcess,不要再依賴Common了,這樣你的項目也好 管理,管理Common的那我的日子過得也輕鬆(這我的其實均可以不須要了,把他的工資加到你頭上不是更好?:D),未來要升級,顧慮也少。
一會兒挖了個大坑,在開篇裏扯了一些淡。
嗯,乾貨會在後續的系列文章裏面撲面而來的!