http://oncenote.com/2015/12/08/How-to-build-UI/html
http://www.gameres.com/thread_484046_1_1.html前端
寫界面能夠說是每位移動應用開發者的基本功,也是一位合格移動應用開發者繞不過去的坎。但就如不是每一位開發者都可以成爲合格 的開發者同樣,本人在不一樣的團隊中發現,甚少有人可以編寫出合格的UI代碼;而很是奇怪的是,在不少的開發者論壇上看到咱們移動 開發者更多關注於某個控件或者是動畫,但卻不多看到深刻剖析UI機制,指導UI開發的文章。
因爲界面涉及到的方面實在過於普遍,本文不可能事無鉅細,一一道來,因此本文先立足於點,深刻剖析iOS UI系統中不被重視卻很是 重要的機制,幫助本文讀者對iOS的UI系統有總體瞭解;進而以點帶面,拓展到UI邏輯設計和架構設計模式的討論;最後讀文而有所思有 所得,設計開發出高效、易用、流暢的UI模塊。
程序員
<ignore_js_op>數據庫
1. 基礎與本質
終端App開發區別於後端開發最大的不一樣,就是終端開發很大部分的邏輯是爲用戶提供界面以供人機交互,即所謂的UI(User Interface) 。因此全部的UI架構主要關注三大模塊:界面佈局管理,渲染及動畫、事件響應;
1.1 佈局管理
即在規定的座標系統上,按照必定的層級順序位置大小排布在容器內。一個UI系統必然有個基於座標的佈局管理系統,無論是Windows、 Sysbian,仍是Andorid、iOS。好的佈局管理機制直接影響界面邏輯實現的難易程度;
咱們如今平常接觸到的App的UI座標系統都是二維的,咱們如今玩的3D遊戲,受限於二維的展現屏幕,因此實質上只是三維在二維上的映 射投影。咱們一直在往更高的維度發展:全息影像、Hololens等等。在此能夠設想下,將來咱們構建界面的佈局管理極可能就是基於真 實三維座標。
1.2 動畫及渲染
UI之因此叫User Interface,就是由於UI經過視覺上的展現,爲用戶提供信息。這些信息的展現須要經過一系列複雜的計算,最後操做 液晶體展現在顯示屏上,這一系列過程就是渲染和動畫;
下圖就是應用界面渲染到展現的流程:
1.3 事件響應
UI除了展現信息以外,還須要接收並響應用戶的點擊、手勢、搖晃等事件,通過一系列操做後更新展現信息,展現給用戶;正確及時地 響應用戶的操做並給予反饋,是良好用戶體驗的保證。爲什麼Android設備廣泛給人的感受比iOS設備要卡,其中一個主要的緣由是iOS系統 將響應用戶事件放在主線程的最高優先級。
1.4 UI系統架構
從總體理解了上述三個方面,你會對UI架構有系統認識。iOS中的UI系統架構以下:
後端
<ignore_js_op>設計模式
2. View
UIView是UIKit中最基本控件,就如同NSObject基本上是Cocoa庫內全部類的基類同樣,UIView也是UIKit中全部界面控件的基類。只要你 願意,你甚至只用UIView就能夠搭建你的App(不過iOS9作了約束,必須設置keyWindow的rootViewControler)。
通常來講,熟練掌握經常使用的UIView子類控件(如UIButton, UIImageView, UILabel等)就足以應付90%的界面編碼須要。但想要編寫出高 效、優美的界面代碼,還須要更深刻的瞭解。既然要深刻,本文假設你對UIView已經有了初步的瞭解,至少使用寫過幾個完整的頁面; 基於此設定下,本文討論聚焦於如下幾點:
1) UIView 與 CALayer:討論UIView背後的CALayer,瞭解CALayer與UIView的關係及渲染流程;
2) Offscreen Render:闡述什麼是Offscreen Render(離屏渲染),以及一些避免離屏渲染的方法;
3) UIResponser:討論UIView和UIViewController的父類UIResponser,分析iOS設備上的事件響應鏈;
4) 設計與實踐:結合本人開發實踐經驗,說明在UIView應用中好的設計實踐規則;
參考:View Programming Guide for iOS
2.1 UIView 與 CALayer
咱們應該都知道每一個UIView都包含了一個CALayer,就算你沒直接看過CALayer,應該也使用過。好比給一個View切個圓角: view.layer.cornerRadius = 5.0f;;加個邊框:view.layer.borderWidth = 1.0f; view.layer.borderColor = [UIColor darkGrayColor].CGColor;,這裏使用的layer就是CALayer。
CALayer是QuartzCore庫內的類,是iOS上最基本的繪製單元;而UIView只是CALyer之上的封裝,更準確的來講,UIView是CALyer的簡版 封裝,加上事件處理的集合類。事件處理咱們下一節再討論,這裏的簡版封裝如何理解,爲何不直接使用CALayer?
首先,如上一段所述,CALayer是最基本的繪製單元,每個UIView都有一個CALayer的變量(public var layer: CALayer { get }), UIView的渲染實質就是這個layer的渲染。咱們能夠看看的類定義,裏面有不少屬性(變量)及方法在中能夠找到幾乎如出一轍的對應; 如屬性變量frame、hidden,方法public func convertPoint(p: CGPoint, fromLayer l: CALayer?) -> CGPoint等;但也有更多的屬性 方法是UIView所沒有的,這裏就一一列舉了。咱們能夠看到UIView實際上是把經常使用的接口(屬性和方法)暴露出來了,讓UIView更爲易用 。
其次,咱們知道iOS平臺的Cocoa Touch 是源於OS X平臺的Cocoa),是在Cocoa的基礎上添加了適用於移動手機設備的手勢識別、動畫等 特性;但從底層實現上來講,Cocoa Touch與Cocoa共用一套底層的庫,其中就包括了QuartCore.framework;但QuartCore.framework一 開始就是爲OS X設計的,因此其中有部分特性是不適合作移動設備開發的,好比最重要的座標系統。所以,咱們也就不難理解爲什麼 UIView/NSView在CALayer上作了一層封裝。
以上,是UIView於CALayer的主要的關係。
2.2 Offscreen Render
當你尚在懵懂未知的開發初期,在寫UIScrollView及其子類(UITableView、UICollectionView)時,必定會遇到滾動不流暢,常常卡頓 的狀況;你認真研究代碼,發現你邏輯代碼都放到了異步線程,主線程作的都是渲染界面的活,爲何會卡頓?而後你想老手尋求幫助 ,老手會讓你去掉圓角、半透明和陰影之類,App又重回絲般順滑;你不知道爲何,問老手,他可能會很詳細跟你解釋一通,而後你一 知半解地點點頭,腦中一片茫然;較好的狀況,也許你依稀記得這麼一個詞:離屏渲染(Offscreen Render)。那到底什麼是Offscreen Render?爲何Offscreen Render會致使卡頓?
在第一章的1.2節中有提到渲染的流程圖,咱們再更深刻點,先看看最基本的渲染通道流程:
緩存
<ignore_js_op>網絡
注:iOS的GPU渲染機制是Tile-Based的,而Tile-Based GPU也是如今移動設備的主流;
咱們再來看看須要Offscreen Render的渲染通道流程:
多線程
<ignore_js_op>架構
通常狀況下,OpenGL會將應用提交到Render Server的動畫直接渲染顯示(基本的Tile-Based渲染流程),但對於一些複雜的圖像動畫的 渲染並不能直接渲染疊加顯示,而是須要根據Command Buffer分通道進行渲染以後再組合,這一組合過程當中,就有些渲染通道是不會直 接顯示的;對比基本渲染通道流程和Masking渲染通道流程圖,咱們能夠看到到Masking渲染須要更多渲染通道和合並的步驟;而這些沒 有直接顯示在屏幕的上的通道(如上圖的 Pass 1 和 Pass 2)就是Offscreen Rendering Pass。
Offscreen Render爲何卡頓,從上圖咱們就能夠知道,Offscreen Render須要更多的渲染通道,並且不一樣的渲染通道間切換須要耗費 必定的時間,這個時間內GPU會閒置,當通道達到必定數量,對性能也會有較大的影響;
那哪些狀況會Offscreen Render呢?
注:layer.cornerRadius,layer.borderWidth,layer.borderColor並不會Offscreen Render,由於這些不須要加入Mask。
還有更多與Offscreen Render以及動畫圖形優化相關的知識,請認真觀看WWDC。
2.3 設計與實踐
以上幾節,對View在開發過程當中常常遇到,但並不容易深刻理解的概念進行了討論。接下來,我想脫離View的具體概念,談談本人在 View設計和開發中的一些實踐經驗;
2.3.1 精簡扁平的View層次結構
複雜的View層次結果不只會影響渲染效率,並且也會形成代碼的臃腫,會形成不可預料的問題而且難以定位;怎麼樣維護一個精簡扁平 的View層次結構呢?原則以下:
1) 儘可能使用系統原生的控件;
如實現一個icon跟title上下佈局的按鈕,不少人習慣是使用一個view包含了一個UIButton和一個UILabel。實際上更爲推薦的方式是調 整UIButon的contentInset/titleInset/imageInset三個參數來達到這個效果,很是簡單,而且title有UIButton上的展現方式和特性, 如能夠設置高亮顏色等;
又好比一個有着複雜一點佈局結構的滾動界面,有些開發者會以爲使用UITableView/UICollectionView實現會比較複雜,有些效果可能 沒辦法達到,就用他們的基類UIScrollView來實現,本身造了一大套的輪子,代碼可能也變得很是複雜;實際上根據個人經驗,經過重 寫或者是內部屬性的調整是徹底可使用UITableView/UICollectionView來達到這個效果,畢竟UITableView/UICollectionView是 UIScrollView的子類,功能不會減小,而會更增強大,而且咱們還能利用已有的data source和delegate機制,實現設計上的解耦。
其餘常見的還有UINavigationBar、UITabBar、UIToolBar等等;
2) 合理添加/刪除動態View;
有些View是動態的,就是偶爾顯示,偶爾隱藏。這類View有兩種處理方式:增刪,或者顯示/隱藏。沒有標準的答案,我的更推薦增刪的 處理方式,即在有須要的時候添加到對應的ContainerView上,在不須要的時候將其刪除。這樣便可以與懶加載結合在一塊兒,並且也能避 免兩個動態View的相互影響,好比TableFooterView,或者是錯誤加載View。但這並非惟一的方式,假如這個動態View所在的View層級 比較簡單,而且須要動畫進行動態展現,則使用顯示/隱藏也是不錯的處理方式。
2.3.2 通用控件;
每個程序員均可以創建本身的代碼庫,同理,每一位移動開發程序員均可以創建本身的通用控件代碼庫。這個庫內的控件,能夠是你 本身寫的,也能夠是優秀的第三方開源控件。創建控件庫,除了可以避免從新造輪子,大大提升咱們的開發效率,還有更爲重要的一點 :在運用、改造、重構中掌握接口設計解耦,甚至是架構的知識和經驗。
每一個App的UI設計、交互、佈局和配色每每千差萬別,但總脫離不出移動App這一範疇,也就決定了在某些通用的控件交互上會保持一致 性,以讓用戶依據本身在移動應用上的使用經驗就能輕鬆快速上手使用,這就是App的移動性。因此通用控件的適用場景每每是很「通用 」的。好比下拉刷新、加載更多、Tab Bar、提示Tips、加載錯誤從新加載等等。在新的App或者功能模塊上運用這些控件時,你就會思 考怎麼讓控件更加通用,即不影響舊的邏輯,又可以適用新的需求,這對於作界面的架構設計是很是好的鍛鍊。
2.3.3 合理運用VC在替代View組合複雜界面;
在界面開發過程當中,咱們經常會遇到複雜的界面,好比多頁界面、多種佈局方式展現多業務的首頁等,但因爲很大部分開發者已經對「 一屏就是一個VC」這一初學者的習慣奉爲教條,寫出一個龐然大View,再加上覆雜的邏輯代碼,這一塊的代碼極可能就演變成了誰都不 敢動的禁區。一個VC能夠管理多個VC,因此合理的使用VC來替代View進行復雜界面組合,不只可以將複雜界面切分紅更小的粒度,邏輯 代碼也同步合理劃分,便於維護和重構;而依託VC的機制,還能View和數據的動態加載管理。
下一章中關於輕VC的討論是這一節知識的拓展。
3. ViewController
上一節關於View的章節已討論了iOS界面機制,這一節則主要是來談談在寫界面過程當中的設計問題和基本規範;
ViewController在iOS只是一個很是重要的概念,它是咱們在開發界面時最常打交道的模塊,其在一個App中所扮演的角色,View Controller Programming Guide for iOS 中有清晰準確的描述:
1) View Management:管理View;
2) Data Marshalling:管理數據;
3) User Interactions:響應用戶交互;
4) Resource Management:管理資源;
5) Adaptivity:適配不一樣的屏幕尺寸空間的變化;
能夠看到,ViewController有太多的事情要作,這也就致使了ViewController很是容易變得代碼膨脹、邏輯混亂等問題;依照我的經驗 ,一個ViewController類的有效代碼超過500行,這個ViewController就會變得難以維護,但實際上在開發過程當中,每每會遇到上1K行, 甚至2~3K行的ViewController類;當一個ViewController類達到2~3K行,就意味着其餘開發者接手這個模塊來修改東西,已經沒法經過 滾動來定位代碼,只能經過搜索;
因此,在進行界面開發時,ViewController須要特別注意模塊設計,將不一樣的模塊按照邏輯進行必定的拆分,即解耦,又防止 ViewController模塊的代碼膨脹。這就是輕VC的理念;
3.1 輕VC
輕VC是前兩年很是火的名詞,如今彷佛已經成爲了一種業界規範或者是慣例。同上所述,一個VC的類,若是有效代碼超過了500行,則表 示這個類看是變得臃腫而難以維護;到達800行,只能經過搜索來定位代碼時,重構已勢在必行;
關於輕VC,objc.io的開篇第一章#Issue 1 : Lighter View Controllers,足見這一理念的重要性。掌握輕VC的理念基本上是一個iOS開 發者從初級邁向高級必備技能。#Issue 1 : Lighter View Controllers 文中介紹了構建輕VC幾種常見的方式:
1) 將數據源等複雜接口從VC中剝離;
2) 把業務邏輯代碼抽象到Model層;
3) 將複雜View抽象成獨立的類;
4) 使用VC的Containment的特色,將一個VC中邏輯分離的界面模塊剝離成爲多個子VC;
想要設計出合理而易於理解和維護的輕VC結構,須要掌握輕VC的知識並有必定實踐經驗。在如下狀況下,能夠考慮將一個VC設計或者重 構成更多模塊更多類的輕VC結構:
1) 如上所述,代碼超過500行時;
2) VC內的View的數據源來自多個不一樣的地方;
3) VC內有多個複雜的View,須要展現數據實體類較爲複雜;
總之,當你感受你的VC已經變得臃腫,那麼就可嘗試輕VC的實踐,實踐纔有收穫。
3.2 VC的設計
相對於View關注於佈局和展現,VC更關注設計和管理。本節以一個實例,來簡單介紹在一個完整App中的VC設計。
先來看一個常見的UI結構設計例子:
<ignore_js_op>
這個圖應該很是容易理解:最底部是一個側滑抽屜控件,該抽屜包含了App內容展現的TabBarController和設置的VC;TabBarController 的子Item VC包含了相應業務的List VC,點擊List VC進入到詳情View內;有些詳情VC是使用WebViewController來進行內容的展現。非 常簡單,不是麼?接下來講明該設計的洞見:
1) Root ViewController,是整個App內Window的根VC,這是一個生命週期與App相同的VC,即Window的RootViewController是惟一且一 直存在的,須要切換場景則使用這個Root VC控制子VC切換來實現(常見於場景:須要進行強登陸,即登陸以後才能使用的App,登陸成 功後從登陸界面切換到主界面,則登陸VC和主界面VC都應該是Root VC的子VC,受Root VC的控制來進行切換)。這個 RootViewController建議是一個UINavigationController,以此保證足夠擴展性,並提供更爲豐富的界面交互選擇。這個Root VC的生命 週期與App一致,這樣一些突發的靈活分支界面能夠很好的展現在Root VC上,如全局的Loading提示、OpenURL的分支調整等;
2) Main ViewController:主界面,是主要業務展現界面的根界面。該VC與RootVC功能上會很容易重合在一塊兒,但須要注意的是,該VC 並不是一直存在,但切換到一些特定分支時,該VC會從Root VC上remove掉,好比前面所說的強登陸App,登陸界面與主界面就會須要進行 切換。另外,該VC隔離了主要業務展現界面的VC與Root VC,便於App總體界面風格的改版和重構。好比如今上圖展現的是一個側滑抽屜 +TabBar的組合,那到下個版本改版把側滑抽屜去掉,那麼只須要使用TabBar替換DrawerMenu VC在Main VC中的位置便可,而不會影響到 RootVC中其餘分支展現出來的界面(如Push等)。
3) TabBarItem ViewController:做爲TabBar Controller的子Item VC,一般會設計爲NavigationController,用以管理各TabBarItem 內的VC棧。
注:若是須要在Push進入二級界面(Detail VC)時隱藏TabBar,只須要設置二級VC的hidesBottomBarWhenPushed = true便可,若是想 更加靈活的控制TabBar,例如進到三級頁面的時候顯示出TabBar(這個場景應該不多見),或者你的TabBar是自定義的,能夠參考我寫 的一個開源控件MZNavTab;
本節所示例的UI結構是一個很是通用的UI結構,市面上除遊戲外60%以上的App都是相似的UI交互(統計來源於我的手機),假如你的UI 交互與此相似而你的UI結構很混亂的話,不如嘗試下這個UI結構設計。
4. MVC、MVP、MVVM
MVC:
<ignore_js_op>
MVP:
<ignore_js_op>
MVVM:
<ignore_js_op>
圖注:
虛線箭頭:表示二者之間是非強依賴關係。如MVC圖,View與Model通常沒有直接聯繫。
虛線矩形:表示該模塊在對應架構設計中的隱性存在。即通常性架構中並無這個角色,但立足於iOS這個平臺,這又是不可或缺的一部 分;
本文並不打算將MVC、MVP、MVVM這個幾個通用架構設計模式的概念通通在這裏敘述一遍,上面三個圖基本上可以很明白地對比出三者之 間的差別。也許與你在網上看到的不盡相同,這是由於以上三圖更立足於iOS平臺。
4.1 MVC
咱們最初看到的MVC設計模式圖多是這樣的:
<ignore_js_op>
而蘋果官方給的MVC的設計模式圖倒是這樣的:
<ignore_js_op>
到底哪一副圖纔是真正的MVC?個人答案只能是:都是。
MVC從施樂帕克實驗室提出至今,已經應用到各類應用開發領域中:Web App能夠用MVC,iOS/Android/Windows客戶端應用也用MVC,Web 前端也在用MVC,等等;這些幾乎涵蓋了咱們常見的開發領域,因此MVC其實已經超越了他本來最初的設計,基於全部涉及展現的應用都 能套上MVC,只不過不一樣的平臺在設計上略有差異。而MVP和MVVM,也不過是MVC的衍生變種,除這二者以外,還有咱們沒怎麼見過的HMVC 、MVA等。
4.2 Model Layer
在討論MVP和MVVM以前,我想先明確一個常常被誤解的概念:Model。因爲Model這個詞太通用化,如數據Model,數據庫Model,這就致使 了Model這一律念理解差別化,簡單的說,就是被玩壞。拋開其餘,咱們來看看常見的定義:
Wikipedia的定義:
MSDN(https://msdn.microsoft.com/en-us/library/ff649643.aspx)中的定義:
上面兩個定義基本一致:Model,管理應用的行爲和數據。
再來看看Apple官方文檔Model-View-Controller的定義:
雖然Apple的官方文檔是定義Model Objects,但它的含義仍是封裝數據以及管理數據相關的邏輯計算;
因此這裏須要明確的一個概念是:在MVC的設計模式中,Model是一個Layer,而不僅是一個數據模型(Data Model)類。整體來講, Model Layer 包含了數據模型,以及管理這些數據相關的邏輯計算,如本地數據變化、數據緩存、從網絡請求數據等業務邏輯。關於這 個問題,還能夠參考這篇文章:《iOS應用架構談 view層的組織和調用方案》。但有一點須要說明:該文章更傾向於從Model Object上 思考Model的定義,由於裏面的關於Model的示例是從數據模型中擴展出業務接口;而本人則更傾向於從Model Layer來思考Model,即 Model並不限於數據模型,能夠是數據管理類(各類Manager)、請求隊列管理等等。
4.3 MVP VS MVVM
上一節關於Model Layer中推薦的文章《iOS應用架構談 view層的組織和調用方案》對MVC和MVVM都作了很是詳細的討論,是一篇很是不 錯的文章,推薦各位閱讀,那麼本節就來講說MVP,以及我爲何更傾向於選擇MVP做爲App架構設計中的設計框架。
回顧下在本章一開始祭出的MVP以及MVVM兩張圖,二者之間有什麼不一樣?
MVVM的VM(View Model)到V(View),比MVP的P(Presenter)到V(View),多了數據綁定。也就是
MVP:是MVC的變種,其中Model和View的定義與MVC的一致,不一樣點在於:MVC的Controller是管理一組Model與View之間交互邏輯,是一 個管理者;而Presenter(展現者)則是Model於View之間的鏈接者,針對特定模塊的View提供對應的格式化的Model數據,將View中的行 爲反饋到Model中。因此MVC中的Controller通常會管理一個或多個Model和一個或多個View,而Presenter則是 M-P-V 一對一,有更細的 粒度和更好的解耦。
從MVP的定義,你會發現MVP與MVVM極其類似,Presenter與View Model扮演的角色基本沒有差異,除了前面所說到綁定機制。但綁定機制 既有很明顯的強大優勢——自動鏈接View和Model,也有很明顯的缺點——更高的耦合度,更復雜的代碼邏輯;但讓人感嘆命運無常的是 :MVVM隨着ReativeCocoa而在iOS平臺煊赫一時,而iOS平臺上甚少有人說起的MVP,在Android平臺卻幾乎成了標準(Android5.0引入了數 據綁定支持,MVVM會在Android平臺有新的發展)。
我爲何傾向於MVP?不過是相比於MVVM雙向綁定的便利,我更但願個人App設計中有更強的靈活性和擴展性。沒有完美的架構設計模式 ,只有適用於你的App業務場景和團隊的設計模式。好比數據邏輯並不複雜、更注重視覺展現的應用,原始的MVC每每是最優解。全部的 MVC衍生出的變種,無非是爲了Solve The Problem。
4.4 架構設計模式應用
不管MVC、MVP仍是MVVM,都是指導咱們進行架構設計的模式,並不是能夠生搬硬套的;並且在實際的應用中,對於這些設計模式總會有不 同的理解,而且須要根據項目需求進行必要的調整;更爲重要的是在咱們App的架構設計中,處理好Model-View-Controller之間的關係 只是基礎,最主要的挑戰來自於複雜的業務邏輯和場景,這纔是體現一個架構師能力所在。
唐巧前不久寫的一篇文章《被誤解的MVC和被神化的MVVM》對MVC和MVVM的實踐的討論應該是體現瞭如今移動端主流架構思想,其中對網 絡請求層、ViewModel 層、Service 層、Storage 層等其它類的提取設計,才決定了一個App架構設計的優劣。
對於架構設計,我準備在下一篇文章,結合本人在iOS/Android兩端的設計經驗,作個深刻的討論,並給出本身的設計範例,供各位討論 參考。這裏先拋出幾個在架構設計中最常思考的點,做爲下一篇文章的引子:
1) 架構是爲了解耦,越鬆的耦合就表明越多的份層,但人的思惟老是更願意接受直線思惟,怎麼解決這個矛盾?
2) 在一個App中,統一(一致)的架構設計可以讓邏輯代碼更健壯,更有利於團隊成員間的溝通和項目維護,但如何解決其和靈活性之 間的矛盾?
3) 架構設計是否只包含邏輯分層?須要設計數據流和多線程麼?
4) 設計模式中的幾大原則;
5 總結
以上四個章節,先從UI總體出發,到剖析UIView幾點重要機制,接着討論怎麼用好VC這個UI中重要的管理角色,最後則漫談了 MVC/MVVM/MVP幾個架構設計模式的異同和實踐應用,想經過以點帶面,讓咱們在關注了具體實現以後,可以脫離出來,從俯視下咱們App 開發更爲總體核心的部分。
相關閱讀: