iOS應用架構談開篇

>>>  技術話題—商業文明的嶄新時代  >>> 簡體     傳統

緣由


之前安居客iOS app的第二版架構大部分內容是我做的,期間有總結了一些經驗。在將近一年之后,前同事zzz在微信朋友圈上發了一個問題:假如問你一個iOS or Android app的架構,你會從哪些方面來說呢?


當時看到這個問題正好在乘公車回家的路上,閑來無聊就答了一把。在zzz在微信朋友圈上追問了幾個問題之后,我覺得有必要開個博客專門來講講一些個人見解。


其實對于iOS客戶端應用的架構來說,復雜度不亞于服務端,但側重點和入手點卻跟服務端不太一樣。比如客戶端應用就不需要考慮類似C10K的問題,正常的app就根本不需要考慮。


這系列文章我會主要專注在iOS應用架構方面,很多方案也是基于iOS技術棧的特點而建立的。因為我個人不是很喜歡寫Java,所以Android這邊的我就不太了解了。如果你是Android開發者,你可以側重看我提出的一些架構思想,畢竟不管做什么,思路是相通的,實現手段不同罷了。


當我們討論客戶端應用架構的時候,我們在討論什么?


其實市面上大部分應用不外乎就是顛過來倒過去地做以下這些事情:

簡單來說就是調API,展示頁面,然后跳轉到別的地方再調API,再展示頁面。


那這特么有毛好架構的?

非也,非也。 ---- 包不同 《天龍八部》


App確實就是主要做這些事情,但是支撐這些事情的基礎,就是做架構要考慮的事情。

  • 調用網絡API

  • 頁面展示

  • 數據的本地持久化

  • 動態部署方案


上面這四大點,稍微細說一下就是:

  • 如何讓業務開發工程師方便安全地調用網絡API?然后盡可能保證用戶在各種網絡環境下都能有良好的體驗?

  • 頁面如何組織,才能盡可能降低業務方代碼的耦合度?盡可能降低業務方開發界面的復雜度,提高他們的效率?

  • 當數據有在本地存取的需求的時候,如何能夠保證數據在本地的合理安排?如何盡可能地減小性能消耗?

  • iOS應用有審核周期,如何能夠通過不發版本的方式展示新的內容給用戶?如何修復緊急bug?


上面幾點是針對App說的,下面還有一些是針對團隊說的:

  • 收集用戶數據,給產品和運營提供參考

  • 合理地組織各業務方開發的業務模塊,以及相關基礎模塊

  • 每日app的自動打包,提供給QA工程師的測試工具


一時半會兒我還是只能想到上面這三點,事實上應該還會有很多,想不起來了。


所以當我們討論客戶端應用架構的時候,我們討論的差不多就是這些問題。


這系列文章要回答那些問題?

這系列文章主要是回答以下這些問題:

  1. 網絡層設計方案?設計網絡層時要考慮哪些問題?對網絡層做優化的時候,可以從哪些地方入手?

  2. 頁面的展示、調用和組織都有哪些設計方案?我們做這些方案的時候都要考慮哪些問題?

  3. 本地持久化層的設計方案都有哪些?優劣勢都是什么?不同方案間要注意的問題分別都是什么?

  4. 要實現動態部署,都有哪些方案?不同方案之間的優劣點,他們的側重點?


本文要回答那些問題?

上面細分出來的四個問題,我會分別在四篇文章里面寫。那么這篇文章就是來講一些通識啥的,也是開個坑給大家討論通識問題的。


架構設計的方法

所有事情最難的時候都是開始做的時候,當你開始著手設計并實現某一層的架構乃至整個app的架構的時候,很有可能會出現暫時的無從下手的情況。以下方法論是我這些年總結出來的經驗,每個架構師也一定都有一套自己的方法論,但一樣的是,不管你采用什么方法,全局觀、高度的代碼審美能力、靈活使用各種設計模式一定都是貫穿其中的。歡迎各位在評論區討論。


第一步:搞清楚要解決哪些問題,并找到解決這些問題的充要條件。


你必須得清楚你要做什么,業務方希望要什么。而不是為了架構而架構,也不是為了體驗新技術而改架構方案。以前是MVC,最近流行MVVM,如果過去的MVC是個好架構,沒什么特別大的缺陷,就不要推倒然后搞成MVVM。


關于充要條件我也要說明一下,有的時候系統提供的函數是需要額外參數的,比如read函數。還有翻頁的時候,當前頁碼也是充要條件。但對于業務方來說,這些充要條件還能夠再縮減。


比如read,需要給出file descriptor,需要給出buf,需要給出size。但是對于業務方來說,充要條件就只要file descriptor就夠了。再比如翻頁,其實業務方并不需要記錄當前頁號,你給他暴露一個loadNextPage這樣的方法就夠了。


搞清楚對于業務方而言的真正充要條件很重要!這決定了你的架構是否足夠易用。另外,傳的參數越少,耦合度相對而言就越小,你替換模塊或者升級模塊所花的的代價就越小。


第二步:問題分類,分模塊


這個不用多說了吧。


第三步:搞清楚各問題之間的依賴關系,建立好模塊交流規范并設計模塊。


關鍵在于建立一套統一的交流規范。這一步很能夠體現架構師在軟件方面的價值觀,雖然存在一定程度上的好壞優劣(比如胖Model和瘦Model),但既然都是架構師了,基本上是不會設計出明顯很爛的方案的,除非這架構師還不夠格。所以這里是架構師價值觀輸出的一個窗口,從這一點我們是能夠看出架構師的素質的。


另外要注意的是,一定是建立一套統一的交流規范,不是兩套,不是多套。你要堅持你的價值觀,不要搖擺不定。要是搞出各種五花八門的規范出來,一方面有不切實際的炫技嫌疑,另一方面也會帶來后續維護的災難。


第四步:推演預測一下未來可能的走向,必要時添加新的模塊,記錄更多的基礎數據以備未來之需。


很多稱職的架構師都會在這時候考慮架構未來的走向,以及考慮做完這一輪架構之后,接下來要做的事情。一個好的架構雖然是功在當代利在千秋的工程,但絕對不是一個一勞永逸的工程。軟件是有生命的,你做出來的架構決定了這個軟件它這一生是坎坷還是幸福。


第五步:先解決依賴關系中最基礎的問題,實現基礎模塊,然后再用基礎模塊堆疊出整個架構。


這一步也是驗證你之前的設計是否合理的一步,隨著這一步的推進,你很有可能會遇到需要對架構進行調整的情況。這個階段一定要吹毛求疵高度負責地去開發,不要得過且過,發現架構有問題就及時調整。否則以后調整的成本就非常之大了。


第六步:打點,跑單元測試,跑性能測試,根據數據去優化對應的地方。


你得用這些數據去向你的boss邀功,你也得用這些數據去不斷調整你的架構。


總而言之就是要遵循這些原則:自頂向下設計(1,2,3,4步),自底向下實現(5),先測量,后優化(6)。


什么樣的架構師是好架構師?

  • 每天都在學習,新技術新思想上手速度快,理解速度快。

做不到這點,你就是碼農。


  • 業務出身,或者至少非常熟悉公司所處行業或者本公司的業務。

做不到這點,你就是運維。


  • 熟悉軟件工程的各種規范,踩過無數坑。不會為了完成需求不擇手段,不推崇quick & dirty。

做不到這點,你比較適合去競爭對手那兒當工程師。


  • 及時承認錯誤,不要覺得承認錯誤會有損你架構師的身份。

做不到這點,公關行業比較適合你。


  • 不為了炫技而炫技

做不到這點,你就是高中編程愛好者。


  • 精益求精

做不到這點,(我想了好久,但我還是不知道你適合去干什么。)


什么樣的架構叫好架構?

  • 代碼整齊,分類明確,沒有common,沒有core

  • 不用文檔,或很少文檔,就能讓業務方上手

  • 思路和方法要統一,盡量不要多元

  • 沒有橫向依賴,萬不得已不出現跨層訪問

  • 對業務方該限制的地方有限制,該靈活的地方要給業務方創造靈活實現的條件

  • 易測試,易拓展

  • 保持一定量的超前性

  • 接口少,接口參數少

  • 高性能


以上是我判斷一個架構是不是好架構的標準,這是根據重要性來排列的。客戶端架構跟服務端架構要考慮的問題和側重點是有一些區別的。下面我會針對每一點詳細講解一下:


代碼整齊,分類明確,沒有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)delegate;


為什么壞?

1. 不要直接返回id或者傳入id,實在不行,用id也比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。另外,如果是高度模塊化的架構,拓展起來將會是一件非常容易的事情。


保持一定量的超前性


這一點能看出架構師是否關注行業動態,是否能準確把握技術走向。保持適度的技術上的超前性,能夠使得你的架構更新變得相對輕松。


另外,這里的超前性也不光是技術上的,還有產品上的。誰說架構師就不需要跟產品經理打交道了,沒事多跟產品經理聊聊天,聽聽他對產品未來走向的暢想,你就可以在合理的地方為他的暢想留一條路子。同時,在創業公司的環境下,很多產品需求其實只是為了趕產品進度而產生的妥協方案,最后還是會轉到正軌的。這時候業務方可以不實現轉到正規的方案,但是架構這邊,是一定要為這種可預知的改變做準備的。


接口少,接口參數少


越少的接口越少的參數,就能越降低業務方的使用成本。當然,充要條件還是要滿足的,如何在滿足充要條件的情況下盡可能地減少接口和參數數量,這就能看出架構師的功力有多深厚了。


高性能


為什么高性能排在最后一位?

高性能非常重要,但是在客戶端架構中,它不是第一考慮因素。原因有下:

  • 客戶端業務變化非常之快,做架構時首要考慮因素應當是便于業務方快速滿足產品需求,因此需要盡可能提供簡單易用效果好的接口給業務方,而不是提供高性能的接口給業務方。

  • 蘋果平臺的性能非常之棒,正常情況下很少會出現由于性能不夠導致的用戶體驗問題。

  • 蘋果平臺的優化手段相對有限,甚至于有些時候即便動用了無所不用其極的手段乃至不擇手段犧牲了穩定性,性能提高很有可能也只不過是100ms到90ms的差距。10%的性能提升對于服務端來說很不錯了,因為服務端動不動就是幾十萬上百萬的訪問量,幾十萬上百萬個10ms是很可觀的。但是對于客戶端的用戶來說,他無法感知這10ms的差別,如果從10s優化成9s用戶還是有一定感知的,但是100ms變90ms,我覺得吧,還是別折騰了。


但是!不重要不代表用不著去做,關于性能優化的東西,我會對應放到各系列文章里面去。比如網絡層優化,那就會在網絡層方案的那篇文章里面去寫,對應每層架構都有每層架構的不同優化方案,我都會在各自文章里面一一細說。


結束

一下子挖了個大坑,在開篇里扯了一些淡。

嗯,干貨會在后續的系列文章里面撲面而來的!


來源:Casa Taloyum



CocoaChina 2015-08-23 08:48:32

[新一篇] 開發者自白:我是如何不花一分錢收獲230萬應用下載量的!

[舊一篇] 大型網站技術架構的演進
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表