《iOS應用架構談 開篇》出來以後,不少人來催我趕忙出第二篇。這一篇文章出得至關艱難,由於公司裏的破事兒特別多,我本身又有點私事兒,以致於能用來寫博客的時間不夠充分。html
如今好啦,第二篇出來了。java
當咱們開始設計View層的架構時,每每是這個App尚未開始開發,或者這個App已經發過幾個版本了,而後此時須要作很是完全的重構。ios
通常也就是這兩種時機會去作View層架構,基於這個時機的特殊性,咱們在這時候必須清楚認識到:View層的架構一旦實現或定型,在App發版後可修改的餘地就已經很是之小了。由於它跟業務關聯最爲緊密,因此哪怕稍微動一點點,它所引起的蝴蝶效應都不見得是業務方可以hold住的。這樣的狀況,就要求咱們在實現這個架構時,代碼必須得改得勤快,不能偷懶。也必須抱着充分的自我懷疑態度,作決策時要拿捏好尺度。git
View層的架構很是之重要,在我看來,這部分架構是這系列文章涉及4個方面最重要的一部分,沒有之一。爲何這麼說?程序員
View層架構是影響業務方迭代週期的因素之一github
產品經理產生需求的速度會很是快,尤爲是公司此時仍處於創業初期,在規模稍大的公司裏面,產品經理也喜歡挖大坑來在leader面前刷存在感,好比阿里。這就致使業務工程師任務很是繁重。正常狀況下讓產品經理砍需求是不太可能的,所以做爲架構師,在架構裏有一些可作可不作的事情,最好仍是能作就作掉,不要偷懶。這能夠幫業務方減負,編寫代碼的時候也能更加關注業務。web
我跟一些朋友交流的時候,他們都會或多或少地抱怨本身的團隊迭代速度不夠快,或者說,迭代速度不合理地慢。我認爲迭代速度不是想提就能提的,迭代速度的影響因素有不少,一期PRD裏的任務量和任務複雜度都會影響迭代週期能達到什麼樣的程度。拋開這些外在的不談,從內在可能致使迭代週期達不到合理的速度的緣由來看,其中有一個緣由頗有可能就是View層架構沒有作好,讓業務工程師完成一個不算複雜的需求時,須要處理太多額外的事情。固然,開會多,工程師水平爛也屬於迭代速度提不上去的內部緣由,但這個不屬於本文討論範圍。還有,加班不是優化迭代週期的正確方式
,嗯。面試
通常來講,一個不夠好的View層架構,主要緣由有如下五種:數據庫
這五個地方會影響業務工程師實現需求的效率,進而拖慢迭代週期。View架構的其餘缺陷也會或多或少地產生影響,但在我看來這裏五個是比較重要的影響因素。若是你們以爲還有什麼因素比這四個更高的,能夠在評論區提出來我補上去。xcode
對於第五點我想作一下強調:架構的設計是必定須要有傳承的,有傳承的架構從總體上看會很是協調。但實際狀況有多是一我的走了,另外一個頂上,即使任務交接得再完整,都不可避免不一樣的人有不一樣的架構思路,從而致使整個架構的流暢程度受到影響。要解決這個問題,一方面要儘可能避免單點問題,讓架構師作架構的時候再帶一我的。另外一方面,架構要設計得儘可能簡單,平緩接手人的學習曲線。我離開安居客的時候,作過保證:凡是從我手裏出來的代碼,終身保修
。因此不要想着離職了就什麼事兒都無論了,這不光是職業素養問題,還有一個是你對你的代碼是否足夠自信的問題。傳承性對於View層架構很是重要,由於它距離業務最近,改動餘地最小。
因此當各位CTO、技術總監、TeamLeader們以爲迭代週期不夠快時,你能夠先不忙着急吼吼地去招新人,《人月神話》早就說過加人不能徹底解決問題。這時候若是你能夠回過頭來看一下是否是View層架構不合理,把這個弄好也是優化迭代週期的手段之一。
嗯,至於本系列其餘三項的架構方案對於迭代週期的影響程度,我認爲都不如View層架構方案對迭代週期的影響高,因此這是我認爲View層架構是最重要的其中一個理由。
View層架構是最貼近業務的底層架構
View層架構雖然也算底層,但還沒那麼底層,它跟業務的對接面最廣,影響業務層代碼的程度也最深。在全部的底層都牽一髮的時候,在View架構上牽一髮致使業務層動全身的面積最大。
因此View架構在全部架構中一旦定型,可修改的空間就最小,咱們在一開始考慮View相關架構時,不光要實現功能,還要考慮更多規範上的東西。制定規範的目的一方面是防止業務工程師的代碼腐蝕View架構,另外一方面也是爲了可以有所傳承。按照規範來,總仍是不那麼容易出差池的。
還有就是,架構師一開始考慮的東西也會有不少,不可能在初版就把它們所有實現,對於一個還沒有發版的App來講,初版架構每每是最小完整功能集,那麼在第二版第三版的發展過程當中,架構的迭代任務就頗有可能不僅是你一我的的事情了,相信你一我的也不見得能搞定所有。因此你要跟你的合做者們有所約定。另外,初版出去以後,業務工程師在使用過程當中也會產生不少修改意見,哪些意見是合理的,哪些意見是不合理的,也要經過事先約定的規範來進行篩選,最終決定如何採納。
規範也不是一成不變的,何時槍斃意見,何時改規範,這就要靠各位的技術和經驗了。
以上就是前言。
View代碼結構的規定
關於view的佈局
什麼時候使用storyboard,什麼時候使用nib,什麼時候使用代碼寫View
是否有必要讓業務方統一派生ViewController?
方便View佈局的小工具
MVC、MVVM、MVCS、VIPER
本門心法
跨業務時View的處理
留給評論區各類補
總結
架構師不是寫SDK出來交付業務方使用就沒事兒了的,每家公司必定都有一套代碼規範,架構師的職責也包括定義代碼規範。按照道理來說,定代碼規範應該是屬於通識,放在這裏講的緣由只是由於我這邊須要爲View添加一個規範。
制定代碼規範嚴格來說不屬於View層架構的事情,但它對View層架構將來的影響會比較大,也是屬於架構師在設計View層架構時須要考慮的事情。制定View層規範的重要性在於:
在這一節裏面我不打算從頭開始定義一套規範,蘋果有一套Coding Guidelines,當咱們定代碼結構或規範的時候,首先必定要符合這個規範。
而後,相信你們各自公司裏面也都有一套本身的規範,具體怎麼個規範法其實也是根據各位架構師的經驗而定,我這邊只是建議各位在各自規範的基礎上再加上下面這一點。
viewController的代碼應該差很少是這樣:
要點以下:
全部的屬性都使用getter和setter
不要在viewDidLoad裏面初始化你的view而後再add,這樣代碼就很難看。在viewDidload裏面只作addSubview的事情,而後在viewWillAppear裏面作佈局的事情(勘誤1
),最後在viewDidAppear裏面作Notification的監聽之類的事情。至於屬性的初始化,則交給getter去作。
好比這樣:
#pragma mark - life cycle - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; [self.view addSubview:self.firstTableView]; [self.view addSubview:self.secondTableView]; [self.view addSubview:self.firstFilterLabel]; [self.view addSubview:self.secondFilterLabel]; [self.view addSubview:self.cleanButton]; [self.view addSubview:self.originImageView]; [self.view addSubview:self.processedImageView]; [self.view addSubview:self.activityIndicator]; [self.view addSubview:self.takeImageButton]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; CGFloat width = (self.view.width - 30) / 2.0f; self.originImageView.size = CGSizeMake(width, width); [self.originImageView topInContainer:70 shouldResize:NO]; [self.originImageView leftInContainer:10 shouldResize:NO]; self.processedImageView.size = CGSizeMake(width, width); [self.processedImageView right:10 FromView:self.originImageView]; [self.processedImageView topEqualToView:self.originImageView]; CGFloat labelWidth = self.view.width - 100; self.firstFilterLabel.size = CGSizeMake(labelWidth, 20); [self.firstFilterLabel leftInContainer:10 shouldResize:NO]; [self.firstFilterLabel top:10 FromView:self.originImageView]; ... ... }
這樣即使在屬性很是多的狀況下,仍是可以保持代碼整齊,view的初始化都交給getter去作了。總之就是儘可能不要出現如下的狀況:
- (void)viewDidLoad { [super viewDidLoad]; self.textLabel = [[UILabel alloc] init]; self.textLabel.textColor = [UIColor blackColor]; self.textLabel ... ... self.textLabel ... ... self.textLabel ... ... [self.view addSubview:self.textLabel]; }
這種作法就不夠乾淨,都扔到getter裏面去就行了。關於這個作法,在唐巧的技術博客裏面有一篇文章和我所提倡的作法不一樣,這個我會放在後面詳細論述。
getter和setter所有都放在最後
由於一個ViewController頗有可能會有很是多的view,就像上面給出的代碼樣例同樣,若是getter和setter寫在前面,就會把主要邏輯扯到後面去,其餘人看的時候就要先劃過一長串getter和setter,這樣不太好。而後要求業務工程師寫代碼的時候按照順序來分配代碼塊的位置,先是life cycle
,而後是Delegate方法實現
,而後是event response
,而後纔是getters and setters
。這樣後來者閱讀代碼時就能省力不少。
每個delegate都把對應的protocol名字帶上,delegate方法不要處處亂寫,寫到一塊區域裏面去
好比UITableViewDelegate的方法集就老老實實寫上#pragma mark - UITableViewDelegate
。這樣有個好處就是,當其餘人閱讀一個他並不熟悉的Delegate實現方法時,他只要按住command而後去點這個protocol名字,Xcode就可以馬上跳轉到對應這個Delegate的protocol定義的那部分代碼去,就免得他處處找了。
event response專門開一個代碼區域
全部button、gestureRecognizer的響應事件都放在這個區域裏面,不要處處亂放。
關於private methods,正常狀況下ViewController裏面不該該寫
不是delegate方法的,不是event response方法的,不是life cycle方法的,就是private method了。對的,正常狀況下ViewController裏面通常是不會存在private methods的,這個private methods通常是用於日期換算、圖片裁剪啥的這種小功能。這種小功能要麼把它寫成一個category,要麼把他作成一個模塊,哪怕這個模塊只有一個函數也行。
ViewController基本上是大部分業務的載體,自己代碼已經至關複雜,因此跟業務關聯不大的東西能不放在ViewController裏面就不要放。另一點,這個private method的功能這時候只是你用獲得,可是未來說不定別的地方也會用到,一開始就獨立出來,有利於未來的代碼複用。
爲何要這樣要求?
我見過無數ViewController,代碼佈局亂得一塌糊塗,這裏一個delegate那裏一個getter,而後ViewController的代碼通常都死長死長的,看了就讓人頭疼。
定義好這個規範,就能使得ViewController條理清晰,業務方程序員很可以區分哪些放在ViewController裏面比較合適,哪些不合適。另外,也能夠提升代碼的可維護性和可讀性。
業務工程師在寫View的時候必定逃不掉的就是這個命題。用Frame也好用Autolayout也好,若是沒有精心設計過,佈局部分必定慘不忍睹。
直接使用CGRectMake的話可讀性不好,光看那幾個數字,也沒法知道view和view之間的位置關係。用Autolayout可讀性稍微好點兒,但生成Constraint的長度實在太長,代碼觀感不太好。
Autolayout這邊能夠考慮使用Masonry,代碼的可讀性就能好不少。若是還有使用Frame的,能夠考慮一下使用這個項目。
這個項目裏面提供了Frame相關的方便方法(UIView+LayoutMethods
),裏面的方法也基本涵蓋了全部佈局的需求,可讀性很是好,使用它以後基本能夠和CGRectMake說再見了。由於天貓在最近才切換到支持iOS6,因此以前天貓都是用Frame佈局的,在天貓App中,首頁,範兒部分頁面的佈局就使用了這些方法。使用這些方便方法能起到事半功倍的效果。
這個項目也提供了Autolayout方案下生產Constraints的方便方法(UIView+AEBHandyAutoLayout
),可讀性比原生好不少。我當時在寫這系列方法的時候還不知道有Masonry。知道有Masonry以後我特意去看了一下,發現Masonry功能果真強大。不過這系列方法雖然沒有Masonry那麼強大,可是也夠用了。當時安居客iPad版App所有都是Autolayout來作的View佈局,就是使用的這個項目裏面的方法。可讀性很好。
讓業務工程師使用良好的工具來作View的佈局,能提升他們的工做效率,也能減小bug發生的概率。架構師不光要關心那些高大上的內容,也要多給業務工程師提供方便易用的小工具,才能發揮架構師的價值。
這個問題唐巧的博客裏這篇文章也提到過,個人意見和他是基本一致的。
在這裏我還想補充一些內容:
具備必定規模的團隊化iOS開發(10人以上)有如下幾個特色:
部分複用
的狀況也比較多。
若是這三個特色你一看就明白了,下面的解釋就能夠不用看了。若是你針對個人傾向願意進一步討論的,能夠先看我下面的解釋,看完再說。
同一份代碼文件的做者會有不少,不一樣做者同時修改同一份代碼的狀況也很多見。所以,使用Git進行代碼版本管理時出現Conflict的概率也比較大。
iOS開發過程當中,會遇到最蛋疼的兩種Conflict一個是project.pbxproj
,另一個就是StoryBoard
或XIB
。由於這些文件的內容的可讀性很是差,雖然蘋果在XCode5(如今我有點不肯定是否是這個版本了)中對StoryBoard的文件描述方式作了必定的優化,但只是把可讀性從很是差
提高爲不好
。
然而在StoryBoard中每每包含了多個頁面,這些頁面基本上不太可能都由一我的去完成,若是另外一我的在作StoryBoard的操做的時候,出於某些目的動了一下不屬於他的那個頁面,好比爲了美觀調整了一下位置。而後另一我的也由於要添加一個頁面,而在Storyboard中調整了一下某個其餘頁面的位置。那麼針對這個狀況我除了說個呵呵
之外,我就只能說:祝你好運。
看清楚哦,這還沒動具體的頁頁面內容呢。
但若是使用代碼繪製View,Conflict同樣會發生,可是這種Conflict就好解不少了,你懂的。
需求變化很是頻繁,產品經理一時一個主意,爲了完成需求而針對現有代碼進行微調的狀況,以及針對現有代碼的
部分複用
的狀況也比較多。
我以爲產品經理一時一個主意不是他的錯,他說不定也是被逼的,好比誰都會來摻和一下產品的設計,公司裏的全部人,上至CEO,下至基層員工都有可能對產品設計評頭論足,只要他我的有個地方用得不爽(極大多是我的喜愛)而後又正好跟產品經理比較熟悉可以搭得上話,都會提出各類意見。產品經理躲不起也惹不起,有時也是沒辦法,嗯。
但落實到工程師這邊來,這種狀況就很蛋疼。由於這種改變有時候不光是UI,UI所對應的邏輯也有要改的可能,工程師就會兩邊文件都改,你原來link的那個view如今不link了,而後你的outlet對應也要刪掉,這兩部分只要有一個沒作,編譯經過以後跑一下App,一下子就crash了。看起來這不是什麼大事兒,但很影響心情。
另外,若是出現部分的代碼複用,好比說某頁面下某個View也但願放在另一個頁面裏,相關的操做就不是複製粘貼這麼簡單了,你還得從新link一遍。也很影響心情。
複雜界面元素,複雜動畫交互場景的開發任務比較多。
要是想在基於StoryBoard的項目中作一個動畫,很煩。作幾個複雜界面元素,也很煩。有的時候咱們掛Custom View上去,其實在StoryBoard裏面看來就是一個空白View。而後另一點就是,當你的layout出現問題須要調整的時候,仍是挺難找到問題所在的,尤爲是在複雜界面元素的狀況下。
因此在針對View層這邊的要求時,我也是建議不要用StoryBoard。實現簡單的東西,用Code同樣簡單,實現複雜的東西,Code比StoryBoard更簡單。因此我更加提倡用code去畫view而不是storyboard。
有的時候咱們出於記錄用戶操做行爲數據的須要,或者統一配置頁面的目的,會從UIViewController裏面派生一個本身的ViewController,來執行一些通用邏輯。好比天貓客戶端要求全部的ViewController都要繼承自TMViewController。這個統一的父類裏面針對一個ViewController的全部生命週期都作了一些設置,至於這裏都有哪些設置對於本篇文章來講並不重要。在這裏我想討論的是,在設計View架構時,若是爲了可以達到統一設置或執行統一邏輯的目的,使用派生的手段是有必要的嗎?
我以爲沒有必要,爲何沒有必要?
這兩條緣由是我認爲沒有必要使用派生手段的理由,若是兩條理由你都心照不宣,那麼下面的就能夠不用看了。若是你還有點疑惑,請看下面我來詳細講一下緣由。
爲何使用了派生,業務方的使用成本會提高?
其實不光是業務方的使用成本,架構的維護成本也會上升。那麼具體的成本都來自於哪裏呢?
這裏講的集成成本是這樣的:若是業務方本身開了一個獨立demo,快速完成了某個獨立流程,如今他想把這個現有流程集合進去。那麼問題就來了,他須要把全部獨立的UIViewController改變成TMViewController。那爲何不是一開始就馬上使用TMViewController呢?由於要想引入TMViewController,就要引入整個天貓App全部的業務線,全部的基礎庫,由於這個父類裏面涉及數日貓環境纔有的內容,所謂拔出蘿蔔帶出泥,你要是想簡單繼承一下就能搞定的事情,搭環境就要搞半天,而後這個小Demo才能跑得起來。
對於業務層存在的全部父類來講,它們是很容易跟項目中的其餘代碼糾纏不清的,這使得業務方開發時遇到一個兩難問題:要麼把全部依賴所有搞定,而後基於App環境(好比天貓)下開發Demo
,要麼就是本身Demo寫好以後,按照環境要求改代碼
。這裏的兩難問題都會帶來成本,都會影響業務方的迭代進度。
我不肯定各位所在公司是否會有這樣的狀況,但我能夠在這裏給你們舉一個我在阿里的真實的例子:我最近在開發某濾鏡Demo和相關頁面流程,最終是要合併到天貓這個App裏面去的。使用天貓環境進行開發的話,pod install完全部依賴差很少須要10分鐘,而後打開workspace以後,差很少要再等待1分鐘讓xcode作好索引,而後才能正式開始工做。在這裏要感謝一下則平,由於他在此基礎上作了不少優化,使得這個1分鐘已經比原來的時間短不少了。但若是天貓環境有更新,你就要再重複一次上面的流程,不然 就頗有可能編譯不過。
拜託,我只是想作個Demo而已,不想搞那麼複雜。
新來的業務工程師有的時候不見得都記得每個ViewController都必需要派生自TMViewController而不是直接的UIViewController。新來的工程師他不能直接按照蘋果原生的作法去作事情,他須要額外學習,好比說:全部的ViewController都必須繼承自TMViewController。
儘量少地使用繼承能提升項目的可維護性,具體內容我在《跳出面向對象思想(一) 繼承》裏面說了,在這裏我想偷懶不想把那篇文章裏說過的東西再說一遍。
其實對於業務方來講,主要仍是第一個集成成本比較蛋疼,由於這是長痛,每次要作點什麼事情都會遇到。第二點倒還好,短痛。第三點跟業務工程師沒啥關係。
那麼若是不使用派生,咱們應該使用什麼手段?
個人建議是使用AOP。
在架構師實現具體的方案以前,必需要想清楚幾個問題,而後才能決定採用哪一種方案。是哪幾個問題?
這三個問題按照順序一一解答以後,具體方案就能出來了。
咱們先看第一個問題:
方案的效果,和最終要達到的目的是什麼?
方案的效果應該是:
其實就是要實現不經過業務代碼上對框架的主動迎合,使得業務可以被框架感知
這樣的功能。細化下來就是兩個問題,框架要可以攔截到ViewController的生命週期,另外一個問題就是,攔截的定義時機。
對於方法攔截,很容易想到Method Swizzling
,那麼咱們能夠寫一個實例,在App啓動的時候添加針對UIViewController的方法攔截,這是一種作法。還有另外一種作法就是,使用NSObject的load函數,在應用啓動時自動監聽。使用後者的好處在於,這個模塊只要被項目包含,就可以發揮做用,不須要在項目裏面添加任何代碼。
而後另一個要考慮的事情就是,原有的TMViewController(所謂的父類)也是會提供額外方法方便子類使用的,Method Swizzling
只支持針對現有方法的操做,拓展方法的話,嗯,固然是用Category
啦。
我本人不同意Category的過分使用,但鑑於Category是最典型的化繼承爲組合的手段,在這個場景下仍是適合使用的。還有的就是,關於Method Swizzling
手段實現方法攔截,業界也已經有了現成的開源庫:Aspects,咱們能夠直接拿來使用。
我這邊有個很是很是小的Demo能夠放出來給你們,這個Demo只是一個點睛之筆,有一些話我也寫在這個Demo裏面了,各位架構師們大家能夠基於各自公司App的需求去拓展。
這個Demo不包含Category,畢竟Category仍是得大家本身去寫啊~而後這套方案可以完成原來經過派生手段全部能夠完成的任務,但同時又容許業務方沒必要添加任何代碼,直接使用原生的UIViewController。
而後另外要提醒的是,這方案的目的是消除沒必要要的繼承,雖然不限定於UIViewController,但它也是有適用範圍的,在適用繼承的地方,仍是要老老實實使用繼承。好比你有一個數據模型,是由基本模型派生出的一整套模型,那麼這個時候仍是老老實實使用繼承。至於拿捏什麼時候使用繼承,相信各位架構師必定可以處理好,或者你也能夠參考我前面提到的那篇文章來控制拿捏的尺度。
其實這些都是相對通用的思想,萬變不離其宗的仍是在開篇裏面我提到的那三個角色:數據管理者
,數據加工者
,數據展現者
。這些五花八門的思想,不外乎就是制訂了一個規範,規定了這三個角色應當如何進行數據交換。但同時這些也是爭議最多的話題,因此我在這裏來把幾個主流思想作一個梳理,當你在作View層架構時,可以有個比較好的參考。
MVC(Model-View-Controller)是最老牌的的思想,老牌到4人幫的書裏把它歸成了一種模式,其中Model
就是做爲數據管理者
,View
做爲數據展現者
,Controller
做爲數據加工者
,Model
和View
又都是由Controller
來根據業務需求調配,因此Controller
還負擔了一個數據流調配的功能。正在我寫這篇文章的時候,我看到InfoQ發了這篇文章,裏面提到了一個移動開發中的痛點是:對MVC架構劃分的理解
。我當時沒可以去參加這個座談會,也沒辦法發表我的意見,因此就只能在這裏寫寫了。
在iOS開發領域,咱們應當如何進行MVC的劃分?
這裏面其實有兩個問題:
爲何咱們會糾結於iOS開發領域中MVC的劃分問題?
關於這個,每一個人糾結的點可能不太同樣,我也不知道當時座談會上你們的觀點。但請容許我猜一下:是否是由於UIViewController中自帶了一個View,且控制了View的整個生命週期(viewDidLoad,viewWillAppear...),而在常識中咱們都知道Controller不該該和View有如此緊密的聯繫,因此才致使你們對劃分產生困惑?
,下面我會針對這個猜想來給出個人意見。
在服務端開發領域,Controller和View的交互方式通常都是這樣,好比Yii:
/*
... 數據庫取數據 ... 處理數據 ... */ // 此處$this就是Controller $this->render("plan",array( 'planList' => $planList, 'plan_id' => $_GET['id'], ));
這裏Controller和View之間區分得很是明顯,Controller作完本身的事情以後,就把全部關於View的工做交給了頁面渲染引擎去作,Controller不會去作任何關於View的事情,包括生成View,這些都由渲染引擎代勞了。這是一個區別,但其實服務端View的概念和Native應用View的概念,真正的區別在於:從概念上嚴格劃分的話,服務端其實根本沒有View,拜HTTP協議所賜,咱們平時所討論的View只是用於描述View的字符串(更實質的應該稱之爲數據),真正的View是瀏覽器。
。
因此服務端只管生成對View的描述,至於對View的長相,UI事件監聽和處理,都是瀏覽器負責生成和維護的。可是在Native這邊來看,本來屬於瀏覽器的任務也逃不掉要本身作。那麼這件事情由誰來作最合適?蘋果給出的答案是:UIViewController。
鑑於蘋果在這一層作了不少堅苦卓絕的努力,讓iOS工程師們沒必要親自去實現這些內容。並且,它把全部的功能都放在了UIView上,而且把UIView作成不光能夠展現UI,還能夠做爲容器的一個對象。
看到這兒你明白了嗎?UIView的另外一個身份實際上是容器!UIViewController中自帶的那個view,它的主要任務就是做爲一個容器。若是它全部的相關命名都改爲ViewContainer
,那麼代碼就會變成這樣:
- (void)viewContainerDidLoad { [self.viewContainer addSubview:self.label]; [self.viewContainer addSubview:self.tableView]; [self.viewContainer addSubview:self.button]; [self.viewContainer addSubview:self.textField]; } ... ...
僅僅改了個名字,如今是否是感受清晰了不少?若是再要說詳細一點,咱們日常所認爲的服務端MVC是這樣劃分的:
---------------------------
| C |
| Controller |
| |
---------------------------
/ \
/ \
/ \
------------ ---------------------
| M | | V |
| Model | | Render Engine |
| | | + |
------------ | HTML Files |
---------------------
但事實上,整套流程的MVC劃分是這樣:
---------------------------
| C |
| Controller |
| \ |
| Render Engine |
| + |
| HTML Files |
---------------------------
/ \
/ \ HTML String
/ \
------------ ---------------
| M | | V |
| Model | | Browser |
| | | |
------------ ---------------
由圖中能夠看出,咱們服務端開發在這個概念下,其實只涉及M和C的開發工做,瀏覽器做爲View的容器,負責View的展現和事件的監聽。那麼對應到iOS客戶端的MVC劃分上面來,就是這樣:
----------------------------
| C |
| Controller |
| \ |
| View Container |
----------------------------
/ \
/ \
/ \
------------ ----------------------
| M | | V |
| Model | | UITableView |
| | | YourCustomView |
------------ | ... |
----------------------
惟一區別在於,View的容器在服務端,是由Browser負責,在整個網站的流程中,這個容器放在Browser是很是合理的。在iOS客戶端,View的容器是由UIViewController中的view負責,我也以爲蘋果作的這個選擇是很是正確明智的。
由於瀏覽器和服務端之間的關係很是鬆散,並且他們分屬於兩個不一樣陣營,服務端將對View的描述生成以後,交給瀏覽器去負責展現,然而一旦view上有什麼事件產生,基本上是不多傳遞到服務器(也就是所謂的Controller)的(要傳也能夠:AJAX),都是在瀏覽器這邊把事情都作掉,因此在這種狀況下,View容器就適合放在瀏覽器(V)這邊。
可是在iOS開發領域,雖然也有讓View去監聽事件的作法,但這種作法很是少,都是把事件回傳給Controller,而後Controller再另行調度。因此這時候,View的容器放在Controller就很是合適。Controller能夠由於不一樣事件的產生去很方便地更改容器內容,好比加載失敗時,把容器內容換成失敗頁面的View,無網絡時,把容器頁面換成無網絡的View等等。
在iOS開發領域中,怎樣纔算是MVC劃分的正確姿式?
這個問題其實在上面已經解答掉一部分了,那麼這個問題的答案就當是對上面問題的一個總結吧。
M應該作的事:
C應該作的事:
V應該作的事:
我經過與服務端MVC劃分的對比來回答了這兩個問題,之因此這麼作,是由於我知道有不少iOS工程師以前是從服務端轉過來的。我也是這樣,在進安居客以前,我也是作服務端開發的,在學習iOS的過程當中,我也曾經對iOS領域的MVC劃分問題產生過疑惑,我疑惑的點就是前面開篇我猜想的點。若是有人問我iOS中應該怎麼作MVC的劃分,我就會像上面這麼回答。
蘋果自身就採用的是這種架構思路,從名字也能看出,也是基於MVC衍生出來的一套架構。從概念上來講,它拆分的部分是Model部分,拆出來一個Store。這個Store專門負責數據存取。但從實際操做的角度上講,它拆開的是Controller。
這算是瘦Model的一種方案,瘦Model只是專門用於表達數據,而後存儲、數據處理都交給外面的來作。MVCS使用的前提是,它假設了你是瘦Model,同時數據的存儲和處理都在Controller去作。因此對應到MVCS,它在一開始就是拆分的Controller。由於Controller作了數據存儲的事情,就會變得很是龐大,那麼就把Controller專門負責存取數據的那部分抽離出來,交給另外一個對象去作,這個對象就是Store。這麼調整以後,整個結構也就變成了真正意義上的MVCS。
關於胖Model和瘦Model
我在面試和跟別人聊天時,發現知道胖Model和瘦Model的概念的人不是不少。大約兩三年前國外業界曾經對此有過很是激烈的討論,主題就是Fat model, skinny controller
。如今關於這方面的討論已經很少了,然而直到今天胖Model和瘦Model哪一個更好,業界也尚未定論,因此這算是目前業界懸而未解的一個爭議。我不多看到國內有討論這個的資料,因此在這裏我打算補充一下什麼叫胖Model什麼叫瘦Model。以及他們的爭論來源於何處。
胖Model包含了部分弱業務邏輯
。胖Model要達到的目的是,Controller從胖Model這裏拿到數據以後,不用額外作操做或者只要作很是少的操做,就可以將數據直接應用在View上
。舉個例子:
Raw Data: timestamp:1234567 FatModel: @property (nonatomic, assign) CGFloat timestamp; - (NSString *)ymdDateString; // 2015-04-20 15:16 - (NSString *)gapString; // 3分鐘前、1小時前、一天前、2015-3-13 12:34 Controller: self.dateLabel.text = [FatModel ymdDateString]; self.gapLabel.text = [FatModel gapString];
把timestamp轉換成具體業務上所須要的字符串,這屬於業務代碼,算是弱業務。FatModel作了這些弱業務以後,Controller就能變得很是skinny,Controller只須要關注強業務代碼就好了。衆所周知,強業務變更的可能性要比弱業務大得多,弱業務相對穩定,因此弱業務塞進Model裏面是沒問題的。另外一方面,弱業務重複出現的頻率要大於強業務,對複用性的要求更高,若是這部分業務寫在Controller,相似的代碼會灑獲得處都是,一旦弱業務有修改(弱業務修改頻率低不表明就沒有修改),這個事情就是一個災難。若是塞到Model裏面去,改一處不少地方就能跟着改,就能避免這場災難。
然而其缺點就在於,胖Model相對比較難移植,雖然只是包含弱業務,但好歹也是業務,遷移的時候很容易拔出蘿蔔帶出泥。另一點,MVC的架構思想更加傾向於Model是一個Layer,而不是一個Object,不該該把一個Layer應該作的事情交給一個Object去作。最後一點,軟件是會成長的,FatModel頗有可能隨着軟件的成長愈來愈Fat,最終難以維護。
瘦Model只負責業務數據的表達,全部業務不管強弱一概扔到Controller
。瘦Model要達到的目的是,盡一切可能去編寫細粒度Model,而後配套各類helper類或方法來對弱業務作抽象,強業務依舊交給Controller
。舉個例子:
Raw Data: { "name":"casa", "sex":"male", } SlimModel: @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSString *sex; Helper: #define Male 1; #define Female 0; + (BOOL)sexWithString:(NSString *)sex; Controller: if ([Helper sexWithString:SlimModel.sex] == Male) { ... }
因爲SlimModel跟業務徹底無關,它的數據能夠交給任何一個能處理它數據的Helper或其餘的對象,來完成業務。在代碼遷移的時候獨立性很強,不多會出現拔出蘿蔔帶出泥的狀況。另外,因爲SlimModel只是數據表達,對它進行維護基本上是0成本,軟件膨脹得再厲害,SlimModel也不會大到哪兒去。
缺點就在於,Helper這種作法也不見得很好,這裏有一篇文章批判了這個事情。另外,因爲Model的操做會出如今各類地方,SlimModel在必定程度上違背了DRY(Don't Repeat Yourself)的思路,Controller仍然不可避免在必定程度上出現代碼膨脹。
個人態度?嗯,我會在本門心法
這一節裏面說。
說回來,MVCS是基於瘦Model的一種架構思路,把本來Model要作的不少事情中的其中一部分關於數據存儲的代碼抽象成了Store,在必定程度上下降了Controller的壓力。
VIPER(View,Interactor,Presenter,Entity,Routing)。VIPER我並無實際使用過,我是在objc.io上第13期看到的。
但凡出現一個新架構或者我以前並不熟悉的新架構,有一點我可以很是確定,這貨必定又是把MVC的哪一個部分給拆開了(壞笑,作這種判斷的理論依據在第一篇文章裏面我已經講過了)。事實狀況是VIPER確實拆了不少不少,除了View沒拆,其它的都拆了。
我提到的這兩篇文章關於VIPER都講得很詳細,一看就懂。但具體在使用VIPER的時候會有什麼坑或者會有哪些爭議我不是很清楚,硬要寫這一節的話我只能靠YY,因此我想一想仍是算了。若是各位讀者有誰在實際App中採用VIPER架構的或者對VIPER頗有興趣的,能夠評論區裏面提出來,咱們交流一下。
針對View層的架構不光是看重如何合理地拆分MVC來給UIViewController減負,另一點也要照顧到業務方的使用成本。最好的狀況是業務方什麼都不知道,而後他把代碼放進去就能跑,同時還能得到框架提供的種種功能。
好比天安門廣場上的觀衆看臺,就是我以爲最好的設計,由於沒人會注意到它。
繼承是罪惡,儘可能不要繼承。就我目前瞭解到的狀況看,除了安居客的Pad App沒有在框架級針對UIViewController有繼承的設計之外,其它公司或多或少都針對UIViewController有繼承,包括安居客iPhone app(那時候我已經對此無能爲力,可見View的架構在一開始就設計好有多麼重要)。甚至有的還對UITableView有繼承,這是一件多麼使人髮指,多麼慘絕人寰,多麼喪心病狂的事情啊。雖然不可避免的是有些狀況咱們不得不從蘋果原生對象中繼承,好比UITableViewCell。但我仍是建議儘可能不要經過繼承的方案來給原生對象添加功能,前面提到的Aspect方案和Category方案均可以使用。用Aspect+load來實現重載函數,用Category來實現添加函數,固然,耍點手段用Category來添加property也是沒問題的。這些方案已經覆蓋了繼承的所有功能,並且很是好維護,對於業務方也更加透明,何樂而不爲呢。
不用繼承可能在思路上不會那麼直觀,可是對於不使用繼承帶來的好處是足夠頂得上使用繼承的壞處的。順便在此我要給Category正一下名:業界對於Category的態度比較曖昧,在多種場合(講座、資料文檔)都宣揚過儘量不要使用Category。它們說的都有必定道理,但我認爲Category是蘋果提供的最好的使用集合代替繼承的方案,但針對Category的設計對架構師的要求也很高,請合理使用。並且蘋果也在不少場合使用Category,來把一個本來可能很大的對象,根據不一樣場景拆分紅不一樣的Category,從而提升可維護性。
不使用繼承的好處我在這裏已經說了,放到iOS應用架構來看,還能再多額外兩個好處:1. 在業務方作業務開發或者作Demo時,能夠脫離App環境,或花更少的時間搭建環境。2. 對業務方來講功能更加透明,也符合業務方在開發時的第一直覺。
這主要是爲了提升可維護性。在一個文件很是大的對象中,尤爲要限制好不一樣類型的代碼在文件中的佈局。好比在寫ViewController時,我以前給團隊制定的規範就是前面一段所有是getter setter,而後接下來一段是life cycle,viewDidLoad之類的方法都在這裏。而後下面一段是各類要實現的Delegate,再下面一段就是event response,Button的或者GestureRecognizer的都在這裏。而後後面是private method。通常狀況下,若是作好拆分,ViewController的private method那一段是沒有方法的。後來隨着時間的推移,我發現開頭放getter和setter太影響閱讀了,因此後面改爲全放在ViewController的最後。
Controller會變得龐大的緣由,一方面是由於Controller承載了業務邏輯,MVC的總結者(在正式提出MVC以前,或多或少都有人這麼設計,因此說MVC的設計者不太準確)對Controller下的定義也是承載業務邏輯,因此Controller就是用來幹這事兒的,天經地義。另外一方面是由於在MVC中,關於Model和View的定義都很是明確,不多有人會把一個屬於M或V的東西放到其餘地方。而後除了Model和View之外,還會剩下不少模棱兩可的東西,這些東西從概念上講都算Controller,並且因爲M和V定義得那麼明確,因此直覺上看,這些東西放在M或V是不合適的,因而就往Controller裏面塞咯。
正是因爲上述兩方面緣由致使了Controller的膨脹。咱們再細細思考一下,Model膨脹和View膨脹,要針對它們來作拆分其實都是相對容易的,Controller膨脹以後,拆分就顯得艱難無比。因此若是可以在一開始就儘可能把能不放在Controller作的事情放到別的地方去作,這樣在第一時間就可讓你的那部分未來可能會被拆分的代碼遠離業務邏輯。因此咱們要稍微轉變一下思路:模棱兩可的模塊,就不要塞到Controller去了,塞到V或者塞到M或者其餘什麼地方都比塞進Controller好,便於未來拆分
。
因此關於前面我按下不表的關於胖Model和瘦Model的選擇,個人態度是更傾向於胖Model。客觀地說,業務膨脹以後,代碼規模確定少不了的,無論你技術再好,經驗再豐富,代碼量最多隻能優化,該膨脹仍是要膨脹的,並且優化以後代碼每每也比較難看,使用各類奇技淫巧也是有代價的。因此,針對代碼量優化的結果,每每要麼就是犧牲可讀性,要麼就是犧牲可移植性(通用性),Every magic always needs a pay, you have to make a trade-off.
。
那麼既然膨脹出來的代碼,或者未來有可能膨脹的代碼,無論放在MVC中的哪個部分,最後都是要拆分的,既然早晚要拆分,那不如放Model裏面,這樣未來拆分胖Model也能比拆分胖Cotroller更加容易。在我還在安居客的時候,安居客Pad app承載最複雜業務的ViewController纔不到600行,其餘多數Controller都是在300-400行之間,這就爲後面接手的人下降了很是多的上手難度和維護複雜度。拆分出來的東西都是能夠直接遷移給iPhone app使用的。如今看天貓的ViewControler,動不動就幾千行,看不了多久頭就暈了,問了一下,你們都表示很習慣這樣的代碼長度,攤手。
架構師在公司裏的職級和地位每每都是要高於業務工程師的,架構師的技術實力和經驗每每也都是高於業務工程師的。因此你值得在公司裏得到較高的地位,可是在公司裏的地位高不表明在軟件工程裏面的角色地位也高
。架構師是要爲業務工程師服務的,是他們使喚你而不是你使喚他們
。另外,制定規範一方面是起到約束業務工程師的代碼,但更重要的一點是,這實際上是利用你的能力幫助業務工程師避免他沒法預見的危機,因此地位高有必定的好處,畢竟夏蟲不可語冰,有的時候不見得可以解釋得通,所以高地位隨之而來的就是說服力會比較強。但在軟件工程裏,必定要保持謙卑,必定要多爲業務工程師考慮。
一個不懂這個道理的架構師,設計出來的東西每每複雜難用,由於他只願意作核心的東西,周邊不肯意作的都指望交給業務工程師去作,甚至有的時候就只作了個Demo,而後就交給業務工程師了,業務工程師變成給他打工的了。可是一個懂得這個道理的架構師,設計出來的東西會很是好用,業務方只須要扔不多的參數而後拿結果就行了,這樣的架構才叫好的架構。
舉一個保存圖片到本地的例子,一種作法是提供這樣的接口:- (NSString *)saveImageWithData:(NSData *)imageData
,另外一種是- (NSString *)saveImage:(UIImage *)image
。後者更好,緣由本身想。
你的態度越謙卑,就越能設計出好的架構,這是我設計心法裏的最後一條,也是最重要的一條。即便你如今技術實力不是業界大牛級別的,但只要保持這個心態去作架構,去作設計,就已是合格的架構師了,要成爲業界大牛也會很是快。
其實針對View層的架構設計,仍是要作好三點:代碼規範
,架構模式
,工具集
。
代碼規範對於View層來講意義重大,畢竟View層很是重業務,若是代碼佈局混亂,後來者很難接手,也很難維護。
架構模式具體如何選擇,徹底取決於業務複雜度。若是業務至關至關複雜,那就能夠使用VIPER,若是相對簡單,那就直接MVC稍微改改就行了。每一種已經成爲定式的架構模式不見得都適合各自公司對應的業務,因此須要各位架構師根據狀況去作一些拆分或者改變。拆分通常都不會出現問題,改變的時候,只要別把MVC三個角色搞混就行了,M該作啥作啥,C該作啥作啥,V該作啥作啥,不要亂來。關於大部分的架構模式應該是什麼樣子,這篇文章裏都已經說過了,不過我認爲最重要的仍是後面的心法,模式只是招術,熟悉了心法才能大巧不工
。
View層的工具集主要仍是集中在如何對View進行佈局,以及一些特定的View,好比帶搜索提示的搜索框這種。這篇文章只提到了View佈局的工具集,其它的工具集相對而言是更加取決於各自公司的業務的,各自實現或者使用CocoaPods裏現成的都不是很難。
對於小規模或者中等規模iOS開發團隊來講,作好以上三點就足夠了。在大規模團隊中,有一個額外問題要考慮,就是跨業務頁面調用方案的設計。
跨業務頁面調用是指,當一個App中存在A業務,B業務等多個業務時,B業務有可能會須要展現A業務的某個頁面,A業務也有可能會調用其餘業務的某個頁面。在小規模的App中,咱們直接import其餘業務的某個ViewController而後或者push或者present,是不會產生特別大的問題的。可是若是App的規模很是大,涉及業務數量很是多,再這麼直接import就會出現問題。
-------------- -------------- --------------
| | page call | | page call | |
| Buisness A | <---------> | Buisness B | <---------> | Buisness C |
| | | | | |
-------------- -------------- --------------
\ | /
\ | /
\ | /
\ | /
\ | /
--------------------------------
| |
| App |
| |
--------------------------------
能夠看出,跨業務的頁面調用在多業務組成的App中會致使橫向依賴。那麼像這樣的橫向依賴,若是不去設法解決,會致使什麼樣的結果?
當一個需求須要多業務合做開發時,若是直接依賴,會致使某些依賴層上端的業務工程師在前期空轉,依賴層下端的工程師任務繁重,而整個需求完成的速度會變慢,影響的是團隊開發迭代速度。
當要開闢一個新業務時,若是已有各業務間直接依賴,新業務又依賴某個舊業務,就致使新業務的開發環境搭建困難,由於必需要把全部相關業務都塞入開發環境,新業務才能進行開發。影響的是新業務的響應速度。
當某一個被其餘業務依賴的頁面有所修改時,好比更名,涉及到的修改面就會特別大。影響的是形成任務量和維護成本都上升的結果。
固然,若是App規模特別小,這三點帶來的影響也會特別小,可是在阿里這樣大規模的團隊中,像天貓/淘寶這樣大規模的App,一旦趕上這裏面哪怕其中一件事情,就特麼很坑爹。
讓依賴關係下沉。
怎麼讓依賴關係下沉?引入Mediator模式。
所謂引入Mediator模式來讓依賴關係下沉,實質上就是每次呼喚頁面的時候,經過一箇中間人來召喚另一個頁面,這樣只要每一個業務依賴這個中間人就能夠了,中間人的角色就能夠放在業務層的下面一層,這就是依賴關係下沉。
-------------- -------------- --------------
| | | | | |
| Buisness A | | Buisness B | | Buisness C |
| | | | | |
-------------- -------------- --------------
\ | /
\ | /
\ | / 經過Mediater來召喚頁面
\ | /
\ | /
--------------------------------
| |
| Mediater |
| |
--------------------------------
|
|
|
|
|
--------------------------------
| |
| App |
| |
--------------------------------
當A業務須要調用B業務的某個頁面的時候,將請求交給Mediater,而後由Mediater經過某種手段獲取到B業務頁面的實例,交還給A就好了。在具體實現這個機制的過程當中,有如下幾個問題須要解決:
這個看起來就很是像咱們web開發時候的URL機制,發送一個Get或Post請求,CGI調用腳本把請求分發給某個Controller下的某個Action,而後返回HTML字符串到瀏覽器去解析。蘋果自己也實現了一套跨App調用機制,它也是基於URL機制來運轉的,只不過它想要解決的問題是跨App的數據交流和頁面調用,咱們想要解決的問題是下降各業務的耦合度。
不過咱們還不能直接使用蘋果原生的這套機制,由於這套機制不可以返回對象實例。而咱們但願可以拿到對象實例,這樣不光能夠作跨業務頁面調用,也能夠作跨業務的功能調用。另外,咱們又但願咱們的Mediater也可以跟蘋果原生的跨App調用兼容,這樣就又能幫業務方省掉一部分開發量。
就我目前所知道的狀況,AutoCad旗下某款iOS應用(時間有點久我不記得是哪款應用了,若是你是AutoCad的iOS開發,能夠在評論區補充一下。)就採用了這種頁面調用方式。天貓裏面目前也在使用這套機制,只是這一塊因爲歷史緣由存在新老版本混用的狀況,所以暫時還沒可以很好地發揮應有的做用。
嗯,想問我要Demo的同窗,我能夠很大方地告訴你,沒有。不過我打算抽時間寫一個出來,如今除了已經想好名字叫Summon之外,其它什麼都沒作,哈哈。
我比較習慣一個對象的"私有"屬性寫在extension裏面,而後這些屬性的初始化所有放在getter裏面作,在init和dealloc以外,是不會出現任何相似_property
這樣的寫法的。就是這樣:
@interface CustomObject() @property (nonatomic, strong) UILabel *label; @end @implement #pragma mark - life cycle - (void)viewDidLoad { [super viewDidLoad]; [self.view addSubview:self.label]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.label.frame = CGRectMake(1, 2, 3, 4); } #pragma mark - getters and setters - (UILabel *)label { if (_label == nil) { _label = [[UILabel alloc] init]; _label.text = @"1234"; _label.font = [UIFont systemFontOfSize:12]; ... ... } return _label; } @end
唐巧說他喜歡的作法是用_property
這種,而後關於_property
的初始化經過[self setupProperty]
這種作法去作。從剛纔上面的代碼來看,就是要在viewDidLoad裏面多調用一個setup方法而已,而後我推薦的方法就是不用多調一個setup方法,直接走getter。
嗯,怎麼說呢,其實兩種作法都能完成需求。可是從另外一個角度看,蘋果之因此選擇讓[self getProperty]
和self.property
能夠互相通用,這種作法已經很明顯地表達了蘋果的傾向:但願每一個property都是經過getter方法來得到
。
早在2003年,Allen Holub就發了篇文章《Why getter and setter methods are evil》,自此以後,業界就對此產生了各類爭議,雖然是從Java開始說的,可是發展到後面各類語言也參與了進來。而後雖然如今關於這個問題討論得少了,可是依舊屬於沒有定論的狀態。setter的狀況比較複雜,也不是我這一節的重點,我這邊仍是主要說getter。咱們從objc的設計來看,蘋果的設計者更加傾向於getter is not evil
。
認爲getter is evil
的緣由有很是之多,或大或小,隨着爭論的進行,你們慢慢就聚焦到這樣的一個緣由:Getter和Setter提供了一個能讓外部修改對象內部數據的方式,這是evil的,正常狀況下,一個對象本身私有的變量應該是隻有本身關心
。
而後咱們回到iOS領域來,objc也一樣面臨了這樣的問題,甚至更加嚴重:objc並無像Java那麼嚴格的私有概念
。但在實際工做中,咱們不太會去操做頭文件裏面沒有的變量,這是從規範上就被禁止的。
認爲getter is not evil
的緣由也能夠聚焦到一個:高度的封裝性
。getter事實上是工廠方法,有了getter以後,業務邏輯能夠更加專一於調用,而沒必要擔憂當前變量是否可用。咱們能夠想一下,假設一個ViewController有20個subview要加入view中,這20個subview的初始化代碼是確定逃不掉的,放在哪裏比較好?放在哪裏都比放在addsubview的地方好,我我的認爲最好的地方仍是放在getter裏面,結合單例模式以後,代碼會很是整齊,生產的地方和使用的地方獲得了很好的區分。
因此放到iOS來講,我仍是以爲使用getter會比較好,由於evil的地方在iOS這邊基本都避免了,not evil的地方都能享受到,仍是不錯的。
要作一個View層架構,主要就是從如下三方面入手:
固然,你還會遇到其餘的不少問題,這時候你能夠參考這篇文章裏提出的心法,在後面提到的跨業務頁面調用方案的設計中,你也可以看到個人一些心法的影子。
對於iOS客戶端來講,它並不像其餘語言諸如Python、PHP他們有那麼多的非官方通用框架。客觀緣由在於,蘋果已經爲咱們作了很是多的事情,作了不少的努力。在蘋果已經作了這麼多事情的基礎上,架構師要作針對View層的方案時,最好仍是儘可能遵照蘋果已有的規範和設計思想,而後根據本身過去開發iOS時的經驗,儘量給業務方在開發業務時減負,提升業務代碼的可維護性,就是View層架構方案的最大目標。