iOS-多方位全面解析:如何正確地寫好一個(gè)界面

寫界面可以說是每位移動(dòng)應(yīng)用開發(fā)者的基本功,也是一位合格移動(dòng)應(yīng)用開發(fā)者繞不過去的坎。但就如不是每一位開發(fā)者都能夠成為合格的開發(fā)者一樣,本人在不同的團(tuán)隊(duì)中發(fā)現(xiàn),甚少有人能夠編寫出合格的UI代碼;而非常奇怪的是,在很多的開發(fā)者論壇上看到我們移動(dòng)開發(fā)者更多關(guān)注于某個(gè)控件或者是動(dòng)畫,但卻很少看到深入剖析UI機(jī)制,指導(dǎo)UI開發(fā)的文章。

由于界面涉及到的方面實(shí)在過于廣泛,本文不可能事無巨細(xì),一一道來,所以本文先立足于

點(diǎn),深入剖析iOS

UI系統(tǒng)中不被重視卻非常重要的機(jī)制,幫助本文讀者對(duì)iOS的UI系統(tǒng)有整體了解;進(jìn)而以點(diǎn)帶面,拓展到UI邏輯設(shè)計(jì)和架構(gòu)設(shè)計(jì)模式的討論;最后讀文而有

所思有所得,設(shè)計(jì)開發(fā)出高效、易用、流暢的UI模塊。

本文章節(jié)如下:

基礎(chǔ)與本質(zhì):說明普遍意義上的UI系統(tǒng)的三大模塊,讓讀者從整體上對(duì)UI系統(tǒng)有清楚的認(rèn)識(shí)。

View:深入View的內(nèi)部機(jī)制,View與Layer之間的關(guān)系,以及Offscreen Render;

ViewController:講解ViewController在UI系統(tǒng)中所扮演的角色,以及UI架構(gòu)設(shè)計(jì)中ViewController運(yùn)用和實(shí)踐;

MVC、MVP、MVVM:簡(jiǎn)單分析三種主流的架構(gòu)設(shè)計(jì)模式及其異同,并簡(jiǎn)單提出了一些做架構(gòu)設(shè)計(jì)意見和想法;

總結(jié)。

各章節(jié)間沒有必然的聯(lián)系,讀者可以選擇感興趣章節(jié)閱讀。

1. 基礎(chǔ)與本質(zhì)

終端App開發(fā)區(qū)別于后端開發(fā)最大的不同,就是終端開發(fā)很大部分的邏輯是為用戶提供界面以供人機(jī)交互,即所謂的UI(User Interface)。所以所有的UI架構(gòu)主要關(guān)注三大模塊:界面布局管理,渲染及動(dòng)畫、事件響應(yīng);

1.1 布局管理

即在規(guī)定的坐標(biāo)系統(tǒng)上,按照一定的層級(jí)順序位置大小排布在容器內(nèi)。一個(gè)UI系統(tǒng)必然有個(gè)基于坐標(biāo)的布局管理系統(tǒng),不管是Windows、Sysbian,還是Andorid、iOS。好的布局管理機(jī)制直接影響界面邏輯實(shí)現(xiàn)的難易程度;

們現(xiàn)在日常接觸到的App的UI坐標(biāo)系統(tǒng)都是二維的,我們現(xiàn)在玩的3D游戲,受限于二維的展示屏幕,所以實(shí)質(zhì)上只是三維在二維上的映射投影。我們一直在往

更高的維度發(fā)展:全息影像、Hololens等等。在此可以設(shè)想下,未來我們構(gòu)建界面的布局管理很可能就是基于真實(shí)三維坐標(biāo)。

1.2 動(dòng)畫及渲染

UI之所以叫User Interface,就是因?yàn)閁I通過視覺上的展示,為用戶提供信息。這些信息的展示需要通過一系列復(fù)雜的計(jì)算,最后操作液晶體展示在顯示屏上,這一系列過程就是渲染和動(dòng)畫;

下圖就是應(yīng)用界面渲染到展示的流程:

引自WWDC2014 #419 Advanced Graphics and Animations for iOS Apps

這里不展開來講,推薦沒看過的同學(xué)都認(rèn)真觀看,能夠很好的理解渲染流程和界面優(yōu)化;

推薦資料:

WWDC2014 #419 Advanced Graphics and Animations for iOS Apps

Objc.io的文章:#Objc Issue 3:Views - Getting Pixels onto the Screen

1.3 事件響應(yīng)

UI

除了展示信息之外,還需要接收并響應(yīng)用戶的點(diǎn)擊、手勢(shì)、搖晃等事件,經(jīng)過一系列操作后更新展示信息,展示給用戶;正確及時(shí)地響應(yīng)用戶的操作并給予反饋,是

良好用戶體驗(yàn)的保證。為何Android設(shè)備普遍給人的感覺比iOS設(shè)備要卡,其中一個(gè)主要的原因是iOS系統(tǒng)將響應(yīng)用戶事件放在主線程的最高優(yōu)先級(jí)。

1.4 UI系統(tǒng)架構(gòu)

從整體理解了上述三個(gè)方面,你會(huì)對(duì)UI架構(gòu)有系統(tǒng)認(rèn)識(shí)。iOS中的UI系統(tǒng)架構(gòu)如下:

引自WWDC2014 #419 Advanced Graphics and Animations for iOS Apps

2. ?View

UIView

是UIKit中最基本控件,就如同NSObject基本上是Cocoa庫內(nèi)所有類的基類一樣,UIView也是UIKit中所有界面控件的基類。只要你愿

意,你甚至只用UIView就可以搭建你的App(不過iOS9做了約束,必須設(shè)置keyWindow的rootViewControler)。

般來說,熟練掌握常用的UIView子類控件(如UIButton, UIImageView,

UILabel等)就足以應(yīng)付90%的界面編碼需要。但想要編寫出高效、優(yōu)美的界面代碼,還需要更深入的了解。既然要深入,本文假設(shè)你對(duì)UIView已經(jīng)

有了初步的了解,至少使用寫過幾個(gè)完整的頁面;基于此設(shè)定下,本文討論聚焦于以下幾點(diǎn):

1) UIView 與 CALayer:討論UIView背后的CALayer,了解CALayer與UIView的關(guān)系及渲染流程;

2) Offscreen Render:闡述什么是Offscreen Render(離屏渲染),以及一些避免離屏渲染的方法;

3) UIResponser:討論UIView和UIViewController的父類UIResponser,分析iOS設(shè)備上的事件響應(yīng)鏈;

4) 設(shè)計(jì)與實(shí)踐:結(jié)合本人開發(fā)實(shí)踐經(jīng)驗(yàn),說明在UIView應(yīng)用中好的設(shè)計(jì)實(shí)踐規(guī)則;

參考:View Programming Guide for iOS

2.1 UIView 與 CALayer

我們應(yīng)該都知道每個(gè)UIView都包含了一個(gè)CALayer,就算你沒直接看過CALayer,應(yīng)該也使用過。比如給一個(gè)View切個(gè)圓角:view.layer.cornerRadius = 5.0f;;加個(gè)邊框:view.layer.borderWidth = 1.0f; view.layer.borderColor = [UIColor darkGrayColor].CGColor;,這里使用的layer就是CALayer。

CALayer

是QuartzCore庫內(nèi)的類,是iOS上最基本的繪制單元;而UIView只是CALyer之上的封裝,更準(zhǔn)確的來說,UIView是CALyer的

簡(jiǎn)版封裝,加上事件處理的集合類。事件處理我們下一節(jié)再討論,這里的簡(jiǎn)版封裝如何理解,為什么不直接使用CALayer?

首先,如上一段所述,CALayer是最基本的繪制單元,每一個(gè)UIView都有一個(gè)CALayer的變量(public var layer: CALayer { get }),UIView的渲染實(shí)質(zhì)就是這個(gè)layer的渲染。我們可以看看的類定義,里面有很多屬性(變量)及方法在中可以找到幾乎一模一樣的對(duì)應(yīng);如屬性變量frame、hidden,方法public func convertPoint(p: CGPoint, fromLayer l: CALayer?) -> CGPoint等;但也有更多的屬性方法是UIView所沒有的,這里就一一列舉了。我們可以看到UIView其實(shí)是把常用的接口(屬性和方法)暴露出來了,讓UIView更為易用。

其次,我們知道iOS平臺(tái)的Cocoa Touch是源于OS X平臺(tái)的Cocoa),是在Cocoa的基礎(chǔ)上添加了適用于移動(dòng)手機(jī)設(shè)備的手勢(shì)識(shí)別、動(dòng)畫等特性;但從底層實(shí)現(xiàn)上來說,Cocoa Touch與Cocoa共用一套底層的庫,其中就包括了QuartCore.framework;但QuartCore.framework一開始就是為OS X設(shè)計(jì)的,所以其中有部分特性是不適合做移動(dòng)設(shè)備開發(fā)的,比如最重要的坐標(biāo)系統(tǒng)。因此,我們也就不難理解為何UIView/NSView在CALayer上做了一層封裝。

以上,是UIView于CALayer的主要的關(guān)系。

2.2 Offscreen Render

當(dāng)

你尚在懵懂未知的開發(fā)初期,在寫UIScrollView及其子類(UITableView、UICollectionView)時(shí),一定會(huì)遇到滾動(dòng)不流

暢,經(jīng)常卡頓的情況;你認(rèn)真研究代碼,發(fā)現(xiàn)你邏輯代碼都放到了異步線程,主線程做的都是渲染界面的活,為什么會(huì)卡頓?然后你想老手尋求幫助,老手會(huì)讓你去

掉圓角、半透明和陰影之類,App又重回絲般順滑;你不知道為什么,問老手,他可能會(huì)很詳細(xì)跟你解釋一通,然后你一知半解地點(diǎn)點(diǎn)頭,腦中一片茫然;較好的

情況,也許你依稀記得這么一個(gè)詞:離屏渲染(Offscreen Render)。那到底什么是Offscreen

Render?為什么Offscreen Render會(huì)導(dǎo)致卡頓?

在第一章的1.2節(jié)中有提到渲染的流程圖,我們?cè)俑钊朦c(diǎn),先看看最基本的渲染通道流程:

引自WWDC2014 #419 Advanced Graphics and Animations for iOS Apps

注:iOS的GPU渲染機(jī)制是Tile-Based的,而Tile-Based?GPU也是現(xiàn)在移動(dòng)設(shè)備的主流;

我們?cè)賮砜纯葱枰狾ffscreen Render的渲染通道流程:

引自WWDC2014 #419 Advanced Graphics and Animations for iOS Apps

般情況下,OpenGL會(huì)將應(yīng)用提交到Render

Server的動(dòng)畫直接渲染顯示(基本的Tile-Based渲染流程),但對(duì)于一些復(fù)雜的圖像動(dòng)畫的渲染并不能直接渲染疊加顯示,而是需要根據(jù)

Command

Buffer分通道進(jìn)行渲染之后再組合,這一組合過程中,就有些渲染通道是不會(huì)直接顯示的;對(duì)比基本渲染通道流程和Masking渲染通道流程圖,我們可

以看到到Masking渲染需要更多渲染通道和合并的步驟;而這些沒有直接顯示在屏幕的上的通道(如上圖的 Pass 1 和 Pass

2)就是Offscreen Rendering Pass。

Offscreen Render為什么卡頓,從上圖我們就可以知道,Offscreen Render需要更多的渲染通道,而且不同的渲染通道間切換需要耗費(fèi)一定的時(shí)間,這個(gè)時(shí)間內(nèi)GPU會(huì)閑置,當(dāng)通道達(dá)到一定數(shù)量,對(duì)性能也會(huì)有較大的影響;

那哪些情況會(huì)Offscreen Render呢?

1)?drawRect

2)?layer.shouldRasterize?=?true;

3)?有mask或者是陰影(layer.masksToBounds,?layer.shadow*);

4)Text(UILabel,?CATextLayer,?Core?Text,?etc)

...

注:layer.cornerRadius,layer.borderWidth,layer.borderColor并不會(huì)Offscreen Render,因?yàn)檫@些不需要加入Mask。

還有更多與Offscreen Render以及動(dòng)畫圖形優(yōu)化相關(guān)的知識(shí),請(qǐng)認(rèn)真觀看WWDC。

參考:

WWDC2011 #121 Understanding UIKit Rendering

WWDC2014 #419 Advanced Graphics and Animations for iOS Apps

2.3 設(shè)計(jì)與實(shí)踐

以上幾節(jié),對(duì)View在開發(fā)過程中經(jīng)常遇到,但并不容易深入理解的概念進(jìn)行了討論。接下來,我想脫離View的具體概念,談?wù)劚救嗽赩iew設(shè)計(jì)和開發(fā)中的一些實(shí)踐經(jīng)驗(yàn);

2.3.1 精簡(jiǎn)扁平的View層次結(jié)構(gòu)

復(fù)雜的View層次結(jié)果不僅會(huì)影響渲染效率,而且也會(huì)造成代碼的臃腫,會(huì)造成不可預(yù)料的問題并且難以定位;怎么樣維護(hù)一個(gè)精簡(jiǎn)扁平的View層次結(jié)構(gòu)呢?原則如下:

1) 盡量使用系統(tǒng)原生的控件;

實(shí)現(xiàn)一個(gè)icon跟title上下布局的按鈕,很多人習(xí)慣是使用一個(gè)view包含了一個(gè)UIButton和一個(gè)UILabel。實(shí)際上更為推薦的方式是調(diào)

整UIButon的contentInset/titleInset/imageInset三個(gè)參數(shù)來達(dá)到這個(gè)效果,非常簡(jiǎn)單,并且上面title有

UIButton上的展示方式和特性;

又比如一個(gè)有著負(fù)責(zé)一點(diǎn)布局結(jié)構(gòu)的滾動(dòng)界面,有些開發(fā)者會(huì)覺得使用

UITableView/UICollectionView實(shí)現(xiàn)會(huì)比較復(fù)雜,有些效果可能沒辦法達(dá)到,就用他們的基類UIScrollView來實(shí)現(xiàn),自

己造了一大套的輪子,代碼可能也變得非常復(fù)雜;實(shí)際上根據(jù)我的經(jīng)驗(yàn),通過重寫或者是內(nèi)部屬性的調(diào)整是完全可以使用

UITableView/UICollectionView來達(dá)到這個(gè)效果,畢竟UITableView/UICollectionView是

UIScrollView的子類,功能不會(huì)減少,而會(huì)更加強(qiáng)大,并且我們還能利用已有的data

source和delegate機(jī)制,實(shí)現(xiàn)設(shè)計(jì)上的解耦。

其他常見的還有UINavigationBar、UITabBar、UIToolBar等等;

2) 合理添加/刪除動(dòng)態(tài)View;

些View是動(dòng)態(tài)的,就是偶爾顯示,偶爾隱藏。這類View有兩種處理方式:增刪,或者顯示/隱藏。沒有標(biāo)準(zhǔn)的答案,個(gè)人更推薦增刪的處理方式,即在有需

要的時(shí)候添加到對(duì)應(yīng)的ContainerView上,在不需要的時(shí)候?qū)⑵鋭h除。這樣即可以與懶加載結(jié)合在一起,而且也能避免兩個(gè)動(dòng)態(tài)View的相互影響,

比如TableFooterView,或者是錯(cuò)誤加載View。但這并不是唯一的方式,假如這個(gè)動(dòng)態(tài)View所在的View層級(jí)比較簡(jiǎn)單,并且需要?jiǎng)赢嬤M(jìn)

行動(dòng)態(tài)展示,則使用顯示/隱藏也是不錯(cuò)的處理方式。

2.3.2 通用控件;

每一個(gè)程序員都可以建

立自己的代碼庫,同理,每一個(gè)移動(dòng)開發(fā)程序員都可以建立自己的通用控件代碼庫。這個(gè)庫內(nèi)的控件,可以是你自己寫的,也可以是優(yōu)秀的第三方開源控件。建立控

件庫,除了能夠避免重新造輪子,大大提高我們的開發(fā)效率,還有更為重要的一點(diǎn):在運(yùn)用、改造、重構(gòu)中掌握接口設(shè)計(jì)解耦,甚至是架構(gòu)的知識(shí)和經(jīng)驗(yàn)。

個(gè)App的UI設(shè)計(jì)、交互、布局和配色往往千差萬別,但總脫離不出移動(dòng)App這一范疇,也就決定了在某些通用的控件交互上會(huì)保持一致性,以讓用戶依據(jù)自己

在移動(dòng)應(yīng)用上的使用經(jīng)驗(yàn)就能輕松快速上手使用,這就是App的移動(dòng)性。所以通用控件的適用場(chǎng)景往往是很“通用”的。比如下拉刷新、加載更多、Tab

Bar、提示Tips、加載錯(cuò)誤重新加載等等。在新的App或者功能模塊上運(yùn)用這些控件時(shí),你就會(huì)思考怎么讓控件更加通用,即不影響老的邏輯,又能夠適用

新的需求,這對(duì)于做界面的架構(gòu)設(shè)計(jì)是非常好的鍛煉。

2.3.3 ?合理運(yùn)用VC在替代View組合復(fù)雜界面;

界面開發(fā)過程中,我們常常會(huì)遇到復(fù)雜的界面,比如多頁界面、多種布局方式展示多業(yè)務(wù)的首頁等,但由于很大部分開發(fā)者已經(jīng)對(duì)“一屏就是一個(gè)VC”這一初學(xué)者

的習(xí)慣奉為教條,寫出一個(gè)龐然大View,再加上復(fù)雜的邏輯代碼,這一塊的代碼很可能就演變成了誰都不敢動(dòng)的禁區(qū)。一個(gè)VC可以管理多個(gè)VC,所以合理的

使用VC來替代View進(jìn)行復(fù)雜界面組合,不僅能夠?qū)?fù)雜界面切分成更小的粒度,邏輯代碼也同步合理劃分,便于維護(hù)和重構(gòu);而依托VC的機(jī)制,還能

View和數(shù)據(jù)的動(dòng)態(tài)加載管理。

下一章中關(guān)于輕VC的討論是這一節(jié)知識(shí)的拓展。

3. ?ViewController

上一節(jié)關(guān)于View的章節(jié)已討論了iOS界面機(jī)制,這一節(jié)則主要是來談?wù)勗趯懡缑孢^程中的設(shè)計(jì)問題和基本規(guī)范;

ViewController在iOS只是一個(gè)非常重要的概念,它是我們?cè)陂_發(fā)界面時(shí)最常打交道的模塊,其在一個(gè)App中所扮演的角色,View Controller Programming Guide for iOS中有清晰準(zhǔn)確的描述:

1) View Management:管理View;

2) Data Marshalling:管理數(shù)據(jù);

3) User Interactions:響應(yīng)用戶交互;

4) Resource Management:管理資源;

5) Adaptivity:適配不同的屏幕尺寸空間的變化;

以看到,ViewController有太多的事情要做,這也就導(dǎo)致了ViewController非常容易變得代碼膨脹、邏輯混亂等問題;依照我個(gè)人的

經(jīng)驗(yàn),一個(gè)ViewController類的有效代碼超過500行,這個(gè)ViewController就會(huì)變得難以維護(hù),但實(shí)際上在開發(fā)過程中,往往會(huì)遇

到上1K行,甚至2~3K行的ViewController類;當(dāng)一個(gè)ViewController類達(dá)到2~3K行,就意味著其他開發(fā)者接手這個(gè)模塊來

修改東西,已經(jīng)無法通過滾動(dòng)來定位代碼,只能通過搜索;

所以,在進(jìn)行界面開發(fā)時(shí),ViewController需要特別注意模塊設(shè)計(jì),將不同的模塊按照邏輯進(jìn)行一定的拆分,即解耦,又防止ViewController模塊的代碼膨脹。這就是輕VC的理念;

3.1 輕VC

輕VC是前兩年非常火的名詞,現(xiàn)在似乎已經(jīng)成為了一種業(yè)界規(guī)范或者是慣例。以我的經(jīng)驗(yàn)來看,一個(gè)VC的類,如果有效代碼超過了500行,則表示這個(gè)類看是變得臃腫而難以維護(hù);到達(dá)800行,基本上沒辦法通過滾動(dòng)來定位代碼,只能通過搜索,此時(shí)重構(gòu)已勢(shì)在必行;

關(guān)于輕VC,objc.io的開篇第一章#Issue 1 : Lighter View Controllers,足見這一理念的重要性。掌握輕VC的理念基本上是一個(gè)iOS開發(fā)者從初級(jí)邁向高級(jí)必備技能。#Issue 1 : Lighter View Controllers文中介紹了構(gòu)建輕VC幾種常見的方式:

1) 將數(shù)據(jù)源等復(fù)雜接口從VC中剝離;

2) 把業(yè)務(wù)邏輯代碼抽象到Model層;

3) 將復(fù)雜View抽象成獨(dú)立的類;

4) 使用VC的Containment的特點(diǎn),將一個(gè)VC中邏輯分離的界面模塊剝離成為多個(gè)子VC;

想要設(shè)計(jì)出合理而易于理解和維護(hù)的輕VC結(jié)構(gòu),需要掌握輕VC的知識(shí)并有一定實(shí)踐經(jīng)驗(yàn)。在以下情況下,可以考慮將一個(gè)VC設(shè)計(jì)或者重構(gòu)成更多模塊更多類的輕VC結(jié)構(gòu):

1) 如上所述,代碼超過500行時(shí);

2) VC內(nèi)的View的數(shù)據(jù)源來自多個(gè)不同的地方;

3) VC內(nèi)有多個(gè)復(fù)雜的View,需要展示數(shù)據(jù)實(shí)體類較為復(fù)雜;

總之,當(dāng)你感覺你的VC已經(jīng)變得臃腫,那么就可嘗試輕VC的實(shí)踐,實(shí)踐才有收獲。

3.2 VC的設(shè)計(jì)

相對(duì)于View關(guān)注于布局和展示,VC更關(guān)注設(shè)計(jì)和管理。本節(jié)以一個(gè)實(shí)例,來簡(jiǎn)單介紹在一個(gè)完整App中的VC設(shè)計(jì)。

先來看一個(gè)常見的UI結(jié)構(gòu)設(shè)計(jì)例子:

個(gè)圖應(yīng)該非常容易理解:最底部是一個(gè)側(cè)滑抽屜控件,該抽屜包含了App內(nèi)容展示的TabBarController和設(shè)置的

VC;TabBarController的子Item VC包含了相應(yīng)業(yè)務(wù)的List VC,點(diǎn)擊List

VC進(jìn)入到詳情View內(nèi);有些詳情VC是使用WebViewController來進(jìn)行內(nèi)容的展示。非常簡(jiǎn)單,不是么?接下來說明該設(shè)計(jì)的洞見:

1)Root ViewController,是整個(gè)App內(nèi)Window的根VC,這是一個(gè)生命周期與App相同的VC,即Window的RootViewController是唯一且一直存在的,需要切換場(chǎng)景則使用這個(gè)Root VC控制子VC切換來實(shí)現(xiàn)(常見于場(chǎng)景:需要進(jìn)行強(qiáng)登錄,即登錄之后才能使用的App,登錄成功后從登錄界面切換到主界面,則登錄VC和主界面VC都應(yīng)該是Root VC的子VC,受Root VC的控制來進(jìn)行切換)。這個(gè)RootViewController建議是一個(gè)UINavigationController,以此保證足夠擴(kuò)展性,并提供更為豐富的界面交互選擇。這個(gè)Root VC的生命周期與App一致,這樣一些突發(fā)的靈活分支界面可以很好的展示在Root VC上,如全局的Loading提示、OpenURL的分支調(diào)整等;

2)Main ViewController:主界面,是主要業(yè)務(wù)展示界面的根界面。該VC與RootVC功能上會(huì)很容易重合在一起,但需要注意的是,該VC并非一直存在,但切換到一些特定分支時(shí),該VC會(huì)從Root VC上remove掉,比如前面所說的強(qiáng)登錄App,登錄界面與主界面就會(huì)需要進(jìn)行切換。另外,該VC隔離了主要業(yè)務(wù)展示界面的VC與Root VC,便于App整體界面風(fēng)格的改版和重構(gòu)。比如現(xiàn)在上圖展示的是一個(gè)側(cè)滑抽屜+TabBar的組合,那到下個(gè)版本改版把側(cè)滑抽屜去掉,那么只需要使用TabBar替換到DrawerMenu VC在Main VC中的位置即可,而不會(huì)影響到RootVC中其他分支展示出來的界面(如Push等)。

3)TabBarItem ViewController:作為TabBar Controller的子Item VC,通常會(huì)設(shè)計(jì)為NavigationController,用以管理各TabBarItem內(nèi)的VC棧。注:如果需要在Push進(jìn)入二級(jí)界面(Detail VC)時(shí)隱藏TabBar,只需要設(shè)置二級(jí)VC的hidesBottomBarWhenPushed = true即可,如果想更加靈活的控制TabBar,例如進(jìn)到三級(jí)頁面的時(shí)候顯示出TabBar(這個(gè)場(chǎng)景應(yīng)該很少見),或者你的TabBar是自定義的,可以參考我寫的一個(gè)開源控件MZNavTab

本節(jié)所示例的UI結(jié)構(gòu)是一個(gè)非常通用的UI結(jié)構(gòu),市面上除游戲外60%以上的App都是類似的UI交互(統(tǒng)計(jì)來源于個(gè)人手機(jī)),假如你的UI交互與此類似而你的UI結(jié)構(gòu)很混亂的話,不如嘗試下這個(gè)UI結(jié)構(gòu)設(shè)計(jì)。

4. MVC、MVP、MVVM

MVC

MVP

MVVM

圖注:

虛線箭頭:表示兩者之間是非強(qiáng)依賴關(guān)系。如MVC圖,View與Model一般沒有直接聯(lián)系。

虛線矩形:表示該模塊在對(duì)應(yīng)架構(gòu)設(shè)計(jì)中的隱性存在。即一般性架構(gòu)中并沒有這個(gè)角色,但立足于iOS這個(gè)平臺(tái),這又是不可或缺的一部分;

本文并不打算將MVC、MVP、MVVM這個(gè)幾個(gè)通用架構(gòu)設(shè)計(jì)模式的概念統(tǒng)統(tǒng)在這里敘述一遍,上面三個(gè)圖基本上能夠很明白地對(duì)比出三者之間的差異。也許與你在網(wǎng)上看到的不盡相同,這是因?yàn)橐陨先龍D更立足于iOS平臺(tái)。

4.1 MVC

我們最初看到的MVC設(shè)計(jì)模式圖可能是這樣的:

自[MSDN#ASP.NET - Single-Page Applications: Build Modern, Responsive Web

Apps with

ASP.NET(https://msdn.microsoft.com/en-us/magazine/dn463786.aspx)

而蘋果官方給的MVC的設(shè)計(jì)模式圖卻是這樣的:

到底哪一副圖才是真正的MVC?我的答案只能是:都是。

MVC從施樂帕克實(shí)驗(yàn)室提出至今,已經(jīng)應(yīng)用到各種應(yīng)用開發(fā)領(lǐng)域中:Web App可以用MVC,iOS/Android/Windows客戶端應(yīng)用也用MVC,Web前端也在用MVC,等等;這些幾乎涵蓋了我們常見的開發(fā)領(lǐng)域,所以MVC其實(shí)已經(jīng)超越了他原本最初的設(shè)計(jì),基于所有涉及展示的應(yīng)用都能套上MVC,只不過不同的平臺(tái)在設(shè)計(jì)上略有差別。而MVP和MVVM,也不過是MVC的衍生變種,除這兩者之外,還有我們沒怎么見過的HMVCMVA等。

4.2 Model Layer

在討論MVP和MVVM之前,我想先明確一個(gè)經(jīng)常被誤解的概念:Model。由于Model這個(gè)詞太通用化,如數(shù)據(jù)Model,數(shù)據(jù)庫Model,這就導(dǎo)致了Model這一概念理解差異化,簡(jiǎn)單的說,就是被玩壞。拋開其他,我們來看看常見的定義:

Wikipedia的定義

The?central?component?of?MVC,?the?model,?captures?the?behavior?of?the?application?in?terms?of?its?problem?domain,?independent?of?the?user?interface.[11]?The?model?directly?manages?the?data,?logic?and?rules?of?the?application.

MSDN(https://msdn.microsoft.com/en-us/library/ff649643.aspx)中的定義

Model.?The?model?manages?the?behavior?and?data?of?the?application?domain,?responds?to?requests?for?information?about?its?state?(usually?from?the?view),?and?responds?to?instructions?to?change?state?(usually?from?the?controller).

上面兩個(gè)定義基本一致:Model,管理應(yīng)用的行為和數(shù)據(jù)。

再來看看Apple官方文檔Model-View-Controller的定義

Model?Objects

Model?objects?encapsulate?the?data?specific?to?an?application?and?define?the?logic?and?computation?that?manipulate?and?process?that?data.?For?example,?a?model?object?might?represent?a?character?in?a?game?or?a?contact?in?an?address?book.?A?model?object?can?have?to-one?and?to-many?relationships?with?other?model?objects,?and?so?sometimes?the?model?layer?of?an?application?effectively?is?one?or?more?object?graphs.?Much?of?the?data?that?is?part?of?the?persistent?state?of?the?application?(whether?that?persistent?state?is?stored?in?files?or?databases)?should?reside?in?the?model?objects?after?the?data?is?loaded?into?the?application.?Because?model?objects?represent?knowledge?and?expertise?related?to?a?specific?problem?domain,?they?can?be?reused?in?similar?problem?domains.?Ideally,?a?model?object?should?have?no?explicit?connection?to?the?view?objects?that?present?its?data?and?allow?users?to?edit?that?data—it?should?not?be?concerned?with?user-interface?and?presentation?issues.

Communication:?User?actions?in?the?view?layer?that?create?or?modify?data?are?communicated?through?a?controller?object?and?result?in?the?creation?or?updating?of?a?model?object.?When?a?model?object?changes?(for?example,?new?data?is?received?over?a?network?connection),?it?notifies?a?controller?object,?which?updates?the?appropriate?view?objects.

雖然Apple的官方文檔是定義ModelObjects,但它的含義還是封裝數(shù)據(jù)以及管理數(shù)據(jù)相關(guān)的邏輯計(jì)算;

所以這里需要明確的一個(gè)概念是:在MVC的設(shè)計(jì)模式中,Model是一個(gè)Layer,而不只是一個(gè)數(shù)據(jù)模型(Data Model)類。總體來說,Model Layer 包含了數(shù)據(jù)模型,以及管理這些數(shù)據(jù)相關(guān)的邏輯計(jì)算,如本地?cái)?shù)據(jù)變化、數(shù)據(jù)緩存、從網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)等業(yè)務(wù)邏輯。關(guān)于這個(gè)問題,還可以參考這篇文章:《iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案》。但有一點(diǎn)需要說明:該文章更傾向于從Model Object上思考Model的定義,因?yàn)槔锩娴年P(guān)于Model的示例是從數(shù)據(jù)模型中擴(kuò)展出業(yè)務(wù)接口;而本人則更傾向于從Model Layer來思考Model,即Model并不限于數(shù)據(jù)模型,可以是數(shù)據(jù)管理類(各種Manager)、請(qǐng)求隊(duì)列管理等等。

4.3 MVP VS MVVM

上一節(jié)關(guān)于Model Layer中推薦的文章《iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案》對(duì)MVC和MVVM都做了非常詳細(xì)的討論,是一篇非常不錯(cuò)的文章,推薦各位閱讀,那么本節(jié)就來說說MVP,以及我為什么更傾向于選擇MVP作為App架構(gòu)設(shè)計(jì)中的設(shè)計(jì)框架。

回顧下在本章一開始祭出的MVP以及MVVM兩張圖,兩者之間有什么不同?

MVVM的VM(View Model)到V(View),比MVP的P(Presenter)到V(View),多了數(shù)據(jù)綁定。也就是

MVP:是MVC的變種,其中Model和View的定義與MVC的一致,不同點(diǎn)在于:MVC的Controller是管理一組Model與View之間交互邏輯,是一個(gè)管理者;而Presenter(展示者)則是Model于View之間的連接者,針對(duì)特定模塊的View提供對(duì)應(yīng)的格式化的Model數(shù)據(jù),將View中的行為反饋到Model中。所以MVC中的Controller一般會(huì)管理一個(gè)或多個(gè)Model和一個(gè)或多個(gè)View,而Presenter則是 M-P-V 一對(duì)一,有更細(xì)的粒度和更好的解耦。

從MVP的定義,你會(huì)發(fā)現(xiàn)MVP與MVVM極其相似,Presenter與View

Model扮演的角色基本沒有差別,除了前面所說到綁定機(jī)制。但綁定機(jī)制既有很明顯的強(qiáng)大優(yōu)點(diǎn)——自動(dòng)連接View和Model,也有很明顯的缺點(diǎn)——更

高的耦合度,更復(fù)雜的代碼邏輯;但讓人感嘆命運(yùn)無常的是:MVVM隨著ReativeCocoa而在iOS平臺(tái)炙手可熱,而iOS平臺(tái)上甚少有人提及的

MVP,在Android平臺(tái)卻幾乎成了標(biāo)準(zhǔn)(Android5.0引入了數(shù)據(jù)綁定支持,MVVM會(huì)在Android平臺(tái)有新的發(fā)展)。

為什么傾向于MVP?不過是相比于MVVM雙向綁定的便利,我更希望我的App設(shè)計(jì)中有更強(qiáng)的靈活性和擴(kuò)展性。沒有完美的架構(gòu)設(shè)計(jì)模式,只有適用于你的

App業(yè)務(wù)場(chǎng)景和團(tuán)隊(duì)的設(shè)計(jì)模式。比如數(shù)據(jù)邏輯并不復(fù)雜、更注重視覺展示的應(yīng)用,原始的MVC往往是最優(yōu)解。所有的MVC衍生出的變種,無非是為了

Solve The Problem。

4.4 架構(gòu)設(shè)計(jì)模式應(yīng)用

無論MVC、MVP還是

MVVM,都是指導(dǎo)我們進(jìn)行架構(gòu)設(shè)計(jì)的模式,并非可以生搬硬套的;而且在實(shí)際的應(yīng)用中,對(duì)于這些設(shè)計(jì)模式總會(huì)有不同的理解,并且需要根據(jù)項(xiàng)目需求進(jìn)行必要

的調(diào)整;更為重要的是在我們App的架構(gòu)設(shè)計(jì)中,處理好Model-View-Controller之間的關(guān)系只是基礎(chǔ),最主要的挑戰(zhàn)來自于復(fù)雜的業(yè)務(wù)邏

輯和場(chǎng)景,這才是體現(xiàn)一個(gè)架構(gòu)師能力所在。

唐巧前不久寫的一篇文章《被誤解的MVC和被神化的MVVM》對(duì)MVC和MVVM的實(shí)踐的討論應(yīng)該是體現(xiàn)了現(xiàn)在移動(dòng)端主流架構(gòu)思想,其中對(duì)網(wǎng)絡(luò)請(qǐng)求層、ViewModel 層、Service 層、Storage 層等其它類的提取設(shè)計(jì),才決定了一個(gè)App架構(gòu)設(shè)計(jì)的優(yōu)劣。

對(duì)于架構(gòu)設(shè)計(jì),我準(zhǔn)備在下一篇文章,結(jié)合本人在iOS/Android兩端的設(shè)計(jì)經(jīng)驗(yàn),做個(gè)深入的討論,并給出自己的設(shè)計(jì)范例,供各位討論參考。這里先拋出幾個(gè)在架構(gòu)設(shè)計(jì)中最常思考的點(diǎn),作為下一篇文章的引子:

1) 架構(gòu)是為了解耦,越松的耦合就代表越多的份層,但人的思維總是更愿意接受直線思維,怎么解決這個(gè)矛盾?

2) 在一個(gè)App中,統(tǒng)一(一致)的架構(gòu)設(shè)計(jì)能夠讓邏輯代碼更健壯,更有利于團(tuán)隊(duì)成員間的溝通和項(xiàng)目維護(hù),但如何解決其和靈活性之間的矛盾?

3) 架構(gòu)設(shè)計(jì)是否只包含邏輯分層?需要設(shè)計(jì)數(shù)據(jù)流和多線程么?

4) 設(shè)計(jì)模式中的幾大原則;

5 總結(jié)

上四個(gè)章節(jié),先從UI整體出發(fā),到剖析UIView幾點(diǎn)重要機(jī)制,接著討論怎么用好VC這個(gè)UI中重要的管理角色,最后則漫談了MVC/MVVM/MVP

幾個(gè)架構(gòu)設(shè)計(jì)模式的異同和實(shí)踐應(yīng)用,想通過以點(diǎn)帶面,讓我們?cè)陉P(guān)注了具體實(shí)現(xiàn)之后,能夠脫離出來,從俯視下我們App開發(fā)更為整體核心的部分。

參考閱讀:

WWDC2011 #121 Understanding UIKit Rendering

WWDC2014 #419 Advanced Graphics and Animations for iOS Apps

iOS Developer Library#View and Window Architecture

iOS Developer Library#UIKit Framework

iOS Developer Library#Getting Started with Graphics & Animation

#Objc 3-View

#Objc 1 : Lighter View Controllers

被誤解的MVC和被神化的MVVM

iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評(píng)論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,441評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,475評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,834評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,009評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,559評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,516評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,728評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,249評(píng)論 3 399
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,484評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容