架構(gòu)設(shè)計之View層

一啄枕、View層架構(gòu)是影響業(yè)務(wù)方迭代周期的因素之一

一期PRD里的任務(wù)量和任務(wù)復(fù)雜度都會影響迭代周期能達到什么樣的程度。拋開這些外在的不談族沃,從內(nèi)在可能導(dǎo)致迭代周期達不到合理的速度的原因來看频祝,其中有一個原因很有可能就是View層架構(gòu)沒有做好,讓業(yè)務(wù)工程師完成一個不算復(fù)雜的需求時脆淹,需要處理太多額外的事情常空。

一般來說,一個不夠好的View層架構(gòu)盖溺,主要原因有以下五種:

  • 代碼混亂不規(guī)范
  • 過多繼承導(dǎo)致的復(fù)雜依賴關(guān)系
  • 模塊化程度不夠高漓糙,組件粒度不夠細(xì)
  • 橫向依賴
  • 架構(gòu)設(shè)計失去傳承

二、View層架構(gòu)是最貼近業(yè)務(wù)的底層架構(gòu)

View層架構(gòu)雖然也算底層烘嘱,但還沒那么底層昆禽,它跟業(yè)務(wù)的對接面最廣,影響業(yè)務(wù)層代碼的程度也最深蝇庭。在所有的底層都牽一發(fā)的時候醉鳖,在View架構(gòu)上牽一發(fā)導(dǎo)致業(yè)務(wù)層動全身的面積最大。

所以View架構(gòu)在所有架構(gòu)中一旦定型哮内,可修改的空間就最小盗棵,我們在一開始考慮View相關(guān)架構(gòu)時,不光要實現(xiàn)功能北发,還要考慮更多規(guī)范上的東西纹因。制定規(guī)范的目的一方面是防止業(yè)務(wù)工程師的代碼腐蝕View架構(gòu),另一方面也是為了能夠有所傳承鲫竞。按照規(guī)范來辐怕,總還是不那么容易出差池的。

還有就是从绘,架構(gòu)師一開始考慮的東西也會有很多寄疏,不可能在第一版就把它們?nèi)繉崿F(xiàn)是牢,對于一個尚未發(fā)版的App來說,第一版架構(gòu)往往是最小完整功能集陕截,那么在第二版第三版的發(fā)展過程中驳棱,架構(gòu)的迭代任務(wù)就很有可能不只是你一個人的事情了,相信你一個人也不見得能搞定全部农曲。所以你要跟你的合作者們有所約定社搅。另外,第一版出去之后乳规,業(yè)務(wù)工程師在使用過程中也會產(chǎn)生很多修改意見形葬,哪些意見是合理的,哪些意見是不合理的暮的,也要通過事先約定的規(guī)范來進行篩選笙以,最終決定如何采納。

三冻辩、View代碼結(jié)構(gòu)的規(guī)定

架構(gòu)師不是寫SDK出來交付業(yè)務(wù)方使用就沒事兒了的猖腕,每家公司一定都有一套代碼規(guī)范,架構(gòu)師的職責(zé)也包括定義代碼規(guī)范恨闪。按照道理來講倘感,定代碼規(guī)范應(yīng)該是屬于通識,放在這里講的原因只是因為我這邊需要為View添加一個規(guī)范咙咽。

制定代碼規(guī)范嚴(yán)格來講不屬于View層架構(gòu)的事情老玛,但它對View層架構(gòu)未來的影響會比較大,也是屬于架構(gòu)師在設(shè)計View層架構(gòu)時需要考慮的事情钧敞。制定View層規(guī)范的重要性在于:

  • 提高業(yè)務(wù)方View層的可讀性可維護性
  • 防止業(yè)務(wù)代碼對架構(gòu)產(chǎn)生腐蝕
  • 確保傳承
  • 保持架構(gòu)發(fā)展的方向不輕易被不合理的意見所左右

首先蘋果有一套《Coding Guidelines》,當(dāng)我們定代碼結(jié)構(gòu)或規(guī)范的時候逻炊,首先一定要符合這個規(guī)范。

然后犁享,相信大家各自公司里面也都有一套自己的規(guī)范,具體怎么個規(guī)范法其實也是根據(jù)各位架構(gòu)師的經(jīng)驗而定豹休,我這邊只是建議各位在各自規(guī)范的基礎(chǔ)上再加上下面這一點炊昆。

規(guī)范.png

不要在viewDidLoad里面初始化你的view然后再add,這樣代碼就很難看威根。在viewDidload里面只做addSubview的事情凤巨,然后在viewWillAppear里面做布局的事情,最后在viewDidAppear里面做Notification的監(jiān)聽之類的事情洛搀。至于屬性的初始化敢茁,則交給getter去做。比如說:

<pre>

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];

    ... ...
    }
    </pre>

其次:

  • getter和setter全部都放在最后

  • 每一個delegate都把對應(yīng)的protocol名字帶上留美,delegate方法不要到處亂寫彰檬,寫到一塊區(qū)域里面去

  • event response專門開一個代碼區(qū)域

  • 關(guān)于private methods伸刃,正常情況下ViewController里面不應(yīng)該寫
    不是delegate方法的,不是event response方法的逢倍,不是life cycle方法的捧颅,就是private method了。對的较雕,正常情況下ViewController里面一般是不會存在private methods的碉哑,這個private methods一般是用于日期換算、圖片裁剪啥的這種小功能亮蒋。這種小功能要么把它寫成一個category扣典,要么把他做成一個模塊,哪怕這個模塊只有一個函數(shù)也行慎玖。
    ViewController基本上是大部分業(yè)務(wù)的載體贮尖,本身代碼已經(jīng)相當(dāng)復(fù)雜,所以跟業(yè)務(wù)關(guān)聯(lián)不大的東西能不放在ViewController里面就不要放凄吏。另外一點远舅,這個private method的功能這時候只是你用得到,但是將來說不定別的地方也會用到痕钢,一開始就獨立出來图柏,有利于將來的代碼復(fù)用。

四任连、關(guān)于View的布局

直接使用CGRectMake的話可讀性很差蚤吹,光看那幾個數(shù)字,也無法知道view和view之間的位置關(guān)系随抠。用Autolayout可讀性稍微好點兒裁着,但生成Constraint的長度實在太長,代碼觀感不太好拱她。Autolayout這邊可以考慮使用Masonry二驰,代碼的可讀性就能好很多。

五秉沼、何時使用storyboard桶雀,何時使用nib,何時使用代碼寫View

我更加提倡用code去畫view而不是storyboard唬复。

六矗积、是否有必要讓業(yè)務(wù)方統(tǒng)一派生ViewController

我覺得沒有必要,為什么沒有必要敞咧?

1.使用派生比不使用派生更容易增加業(yè)務(wù)方的使用成本
2.不使用派生手段一樣也能達到統(tǒng)一設(shè)置的目的

為什么使用了派生棘捣,業(yè)務(wù)方的使用成本會提升?

  • 集成成本
    對于業(yè)務(wù)層存在的所有父類來說休建,它們是很容易跟項目中的其他代碼糾纏不清的乍恐,這使得業(yè)務(wù)方開發(fā)時遇到一個兩難問題:要么把所有依賴全部搞定评疗,然后基于App環(huán)境(下開發(fā)Demo,要么就是自己Demo寫好之后禁熏,按照環(huán)境要求改代碼壤巷。這里的兩難問題都會帶來成本,都會影響業(yè)務(wù)方的迭代進度瞧毙。

  • 上手接受成本
    新來的業(yè)務(wù)工程師有的時候不見得都記得每一個ViewController都必須要派生自TMViewController而不是直接的UIViewController胧华。新來的工程師他不能直接按照蘋果原生的做法去做事情,他需要額外學(xué)習(xí)宙彪,比如說:所有的ViewController都必須繼承自TMViewController矩动。

  • 架構(gòu)的維護難度

那么如果不使用派生,我們應(yīng)該使用什么手段释漆?
我的建議是使用AOP悲没。

在架構(gòu)師實現(xiàn)具體的方案之前,必須要想清楚幾個問題男图,然后才能決定采用哪種方案示姿。是哪幾個問題?

  1. 方案的效果逊笆,和最終要達到的目的是什么栈戳?
  2. 在自己的知識體系里面,是否具備實現(xiàn)這個方案的能力难裆?
  3. 在業(yè)界已有的開源組件里面子檀,是否有可以直接拿來用的輪子?

我們先看第一個問題:方案的效果乃戈,和最終要達到的目的是什么褂痰?
方案的效果應(yīng)該是:

  1. 業(yè)務(wù)方可以不用通過繼承的方法,然后框架能夠做到對ViewController的統(tǒng)一配置症虑。
  2. 業(yè)務(wù)方即使脫離框架環(huán)境缩歪,不需要修改任何代碼也能夠跑完代碼。業(yè)務(wù)方的ViewController一旦丟入框架環(huán)境谍憔,不需要修改任何代碼驶冒,框架就能夠起到它應(yīng)該起的作用。

其實就是要實現(xiàn)不通過業(yè)務(wù)代碼上對框架的主動迎合韵卤,使得業(yè)務(wù)能夠被框架感知這樣的功能。細(xì)化下來就是兩個問題崇猫,框架要能夠攔截到ViewController的生命周期沈条,另一個問題就是,攔截的定義時機诅炉。

對于方法攔截蜡歹,很容易想到Method Swizzling屋厘,那么我們可以寫一個實例,在App啟動的時候添加針對UIViewController的方法攔截月而,這是一種做法汗洒。還有另一種做法就是,使用NSObject的load函數(shù)父款,在應(yīng)用啟動時自動監(jiān)聽溢谤。使用后者的好處在于,這個模塊只要被項目包含憨攒,就能夠發(fā)揮作用世杀,不需要在項目里面添加任何代碼。

然后另外一個要考慮的事情就是肝集,原有的TMViewController(所謂的父類)也是會提供額外方法方便子類使用的瞻坝,Method Swizzling只支持針對現(xiàn)有方法的操作,拓展方法的話杏瞻,嗯所刀,當(dāng)然是用Category啦。

七捞挥、MVC浮创、MVVM等一大堆思想

其實這些都是相對通用的思想,萬變不離其宗的還是在開篇里面我提到的那三個角色:數(shù)據(jù)管理者树肃,數(shù)據(jù)加工者蒸矛,數(shù)據(jù)展示者。這些五花八門的思想胸嘴,不外乎就是制訂了一個規(guī)范雏掠,規(guī)定了這三個角色應(yīng)當(dāng)如何進行數(shù)據(jù)交換。但同時這些也是爭議最多的話題劣像,所以我在這里來把幾個主流思想做一個梳理乡话,當(dāng)你在做View層架構(gòu)時,能夠有個比較好的參考耳奕。

  • MVC

M應(yīng)該做的事:
1.給ViewController提供數(shù)據(jù)
2.給ViewController存儲數(shù)據(jù)提供接口
3.提供經(jīng)過抽象的業(yè)務(wù)基本組件绑青,供Controller調(diào)度

C應(yīng)該做的事:
1.管理View Container的生命周期
2.負(fù)責(zé)生成所有的View實例,并放入View Container
3.監(jiān)聽來自View與業(yè)務(wù)有關(guān)的事件屋群,通過與Model的合作闸婴,來完成對應(yīng)事件的業(yè)務(wù)。

V應(yīng)該做的事:
1.響應(yīng)與業(yè)務(wù)無關(guān)的事件芍躏,并因此引發(fā)動畫效果邪乍,點擊反饋(如果合適的話,盡量還是放在View去做)等。
2.界面元素表達

  • MVCS

蘋果自身就采用的是這種架構(gòu)思路庇楞,從名字也能看出榜配,也是基于MVC衍生出來的一套架構(gòu)。從概念上來說吕晌,它拆分的部分是Model部分蛋褥,拆出來一個Store。這個Store專門負(fù)責(zé)數(shù)據(jù)存取睛驳。但從實際操作的角度上講烙心,它拆開的是Controller

這算是瘦Model的一種方案,瘦Model只是專門用于表達數(shù)據(jù)柏靶,然后存儲弃理、數(shù)據(jù)處理都交給外面的來做。MVCS使用的前提是屎蜓,它假設(shè)了你是瘦Model痘昌,同時數(shù)據(jù)的存儲和處理都在Controller去做。所以對應(yīng)到MVCS炬转,它在一開始就是拆分的Controller辆苔。因為Controller做了數(shù)據(jù)存儲的事情,就會變得非常龐大扼劈,那么就把Controller專門負(fù)責(zé)存取數(shù)據(jù)的那部分抽離出來驻啤,交給另一個對象去做,這個對象就是Store荐吵。這么調(diào)整之后骑冗,整個結(jié)構(gòu)也就變成了真正意義上的MVCS。

關(guān)于胖瘦Model

胖Model包含了部分弱業(yè)務(wù)邏輯先煎。胖Model要達到的目的是贼涩,Controller從胖Model這里拿到數(shù)據(jù)之后,不用額外做操作或者只要做非常少的操作薯蝎,就能夠?qū)?shù)據(jù)直接應(yīng)用在View上遥倦,比如說

<pre>
FatModel:
@property (nonatomic, assign) CGFloat timestamp;
- (NSString *)ymdDateString; // 2015-04-20 15:16
- (NSString *)gapString; // 3分鐘前、1小時前占锯、一天前袒哥、2015-3-13 12:34
</pre>

眾所周知,強業(yè)務(wù)變動的可能性要比弱業(yè)務(wù)大得多消略,弱業(yè)務(wù)相對穩(wěn)定堡称,所以弱業(yè)務(wù)塞進Model里面是沒問題的。另一方面艺演,弱業(yè)務(wù)重復(fù)出現(xiàn)的頻率要大于強業(yè)務(wù)粮呢,對復(fù)用性的要求更高婿失,如果這部分業(yè)務(wù)寫在Controller,類似的代碼會灑得到處都是啄寡,一旦弱業(yè)務(wù)有修改(弱業(yè)務(wù)修改頻率低不代表就沒有修改),這個事情就是一個災(zāi)難哩照。如果塞到Model里面去挺物,改一處很多地方就能跟著改,就能避免這場災(zāi)難飘弧。

然而其缺點就在于识藤,胖Model相對比較難移植,雖然只是包含弱業(yè)務(wù)次伶,但好歹也是業(yè)務(wù)痴昧,遷移的時候很容易拔出蘿卜帶出泥。另外一點冠王,MVC的架構(gòu)思想更加傾向于Model是一個Layer赶撰,而不是一個Object,不應(yīng)該把一個Layer應(yīng)該做的事情交給一個Object去做柱彻。最后一點豪娜,軟件是會成長的,F(xiàn)atModel很有可能隨著軟件的成長越來越Fat哟楷,最終難以維護瘤载。

瘦Model只負(fù)責(zé)業(yè)務(wù)數(shù)據(jù)的表達,所有業(yè)務(wù)無論強弱一律扔到Controller卖擅。瘦Model要達到的目的是鸣奔,盡一切可能去編寫細(xì)粒度Model,然后配套各種helper類或方法來對弱業(yè)務(wù)做抽象惩阶,強業(yè)務(wù)依舊交給Controller挎狸。舉個例子

<pre>
SlimModel:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *sex;

Helper:
#define Male 1;
#define Female 0;
+ (BOOL)sexWithString:(NSString *)sex;
</pre>

由于SlimModel跟業(yè)務(wù)完全無關(guān),它的數(shù)據(jù)可以交給任何一個能處理它數(shù)據(jù)的Helper或其他的對象琳猫,來完成業(yè)務(wù)伟叛。在代碼遷移的時候獨立性很強,很少會出現(xiàn)拔出蘿卜帶出泥的情況脐嫂。另外统刮,由于SlimModel只是數(shù)據(jù)表達,對它進行維護基本上是0成本账千,軟件膨脹得再厲害侥蒙,SlimModel也不會大到哪兒去。

缺點就在于匀奏,由于Model的操作會出現(xiàn)在各種地方鞭衩,SlimModel在一定程度上違背了DRY(Don't Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出現(xiàn)代碼膨脹。

說回來论衍,MVCS是基于瘦Model的一種架構(gòu)思路瑞佩,把原本Model要做的很多事情中的其中一部分關(guān)于數(shù)據(jù)存儲的代碼抽象成了Store,在一定程度上降低了Controller的壓力坯台。

  • MVVM

MVVM是基于胖Model的架構(gòu)思路建立的炬丸,然后在胖Model中拆出兩部分:Model和ViewModel。關(guān)于這個觀點我要做一個額外解釋:胖Model做的事情是先為Controller減負(fù)蜒蕾,然后由于Model變胖稠炬,再在此基礎(chǔ)上拆出ViewModel,跟業(yè)界普遍認(rèn)知的MVVM本質(zhì)上是為Controller減負(fù)這個說法并不矛盾咪啡,因為胖Model做的事情也是為Controller減負(fù)首启。

注意:MVVM的關(guān)鍵是要有View Model!而不是ReactiveCocoa

ViewModel做什么事情撤摸?

就是把RawData變成直接能被View使用的對象的一種Model毅桃。舉個例子:

<pre>
Raw Data:
{
(
(123, 456),
(234, 567),
(345, 678)
)
}
</pre>

這里的RawData我們假設(shè)是經(jīng)緯度,數(shù)字我隨便寫的不要太在意愁溜。然后你有一個模塊是地圖模塊疾嗅,把經(jīng)緯度數(shù)組全部都轉(zhuǎn)變成MKAnnotation或其派生類對于Controller來說是弱業(yè)務(wù),(記住冕象,胖Model就是用來做弱業(yè)務(wù)的)代承,因此我們用ViewModel直接把它轉(zhuǎn)變成MKAnnotation的NSArray,交給Controller之后Controller直接就可以用了渐扮。

那么ReactiveCocoa應(yīng)該扮演什么角色论悴?

不用ReactiveCocoa也能MVVM,用ReactiveCocoa能更好地體現(xiàn)MVVM的精髓墓律。前面我舉到的例子只是數(shù)據(jù)從API到View的方向膀估,View的操作也會產(chǎn)生"數(shù)據(jù)",只不過這里的"數(shù)據(jù)"更多的是體現(xiàn)在表達用戶的操作上耻讽,比如輸入了什么內(nèi)容察纯,那么數(shù)據(jù)就是text、選擇了哪個cell针肥,那么數(shù)據(jù)就是indexPath饼记。那么在數(shù)據(jù)從view走向API或者Controller的方向上,就是ReactiveCocoa發(fā)揮的地方慰枕。

我們知道具则,ViewModel本質(zhì)上算是Model層(因為是胖Model里面分出來的一部分),所以View并不適合直接持有ViewModel具帮,那么View一旦產(chǎn)生數(shù)據(jù)了怎么辦博肋?扔信號扔給ViewModel低斋,用誰扔?ReactiveCocoa匪凡。

在MVVM中使用ReactiveCocoa的第一個目的就是如上所說膊畴,View并不適合直接持有ViewModel。第二個目的就在于病游,ViewModel有可能并不是只服務(wù)于特定的一個View巴比,使用更加松散的綁定關(guān)系能夠降低ViewModel和View之間的耦合度。

那么在MVVM中礁遵,Controller扮演什么角色?

View <-> C <-> ViewModel <-> Model采记,所以使用MVVM之后佣耐,就不需要Controller的說法是不正確的。嚴(yán)格來說MVVM其實是MVCVM唧龄。
Controller夾在View和ViewModel之間做的其中一個主要事情就是將View和ViewModel進行綁定兼砖。在邏輯上,Controller知道應(yīng)當(dāng)展示哪個View既棺,Controller也知道應(yīng)當(dāng)使用哪個ViewModel讽挟,然而View和ViewModel它們之間是互相不知道的,所以Controller就負(fù)責(zé)控制他們的綁定關(guān)系丸冕,所以叫Controller/控制器就是這個原因耽梅。

前面扯了那么多,其實歸根結(jié)底就是一句話:在MVC的基礎(chǔ)上胖烛,把C拆出一個ViewModel專門負(fù)責(zé)數(shù)據(jù)處理的事情眼姐,就是MVVM。然后佩番,為了讓View和ViewModel之間能夠有比較松散的綁定關(guān)系众旗,于是我們使用ReactiveCocoa,因為蘋果本身并沒有提供一個比較適合這種情況的綁定方法趟畏。iOS領(lǐng)域里KVO贡歧,Notification,block赋秀,delegate和target-action都可以用來做數(shù)據(jù)通信利朵,從而來實現(xiàn)綁定啊送,但都不如ReactiveCocoa提供的RACSignal來的優(yōu)雅上渴,如果不用ReactiveCocoa揩懒,綁定關(guān)系可能就做不到那么松散那么好赶促,但并不影響它還是MVVM汰蜘。

所以Controller在MVVM中佳遣,一方面負(fù)責(zé)View和ViewModel之間的綁定质欲,另一方面也負(fù)責(zé)常規(guī)的UI邏輯處理绑嘹。

  • VIPER

八、拆分的心法

  • 第一心法:保留最重要的任務(wù)年碘,拆分其它不重要的任務(wù)

在iOS開發(fā)領(lǐng)域內(nèi)澈歉,UIViewController承載了非常多的事情,比如View的初始化屿衅,業(yè)務(wù)邏輯埃难,事件響應(yīng),數(shù)據(jù)加工等等涤久,當(dāng)然還有更多我現(xiàn)在也列舉不出來涡尘,但是我們知道有一件事情Controller肯定逃不掉要做:協(xié)調(diào)V和M。也就是說响迂,不管怎么拆考抄,協(xié)調(diào)工作是拆不掉的。那么剩下的事情我們就可以拆了蔗彤,比如UITableView的DataSource川梅。這篇文章的第一節(jié)就提倡了這個做法。除了這篇文章提到的內(nèi)容以外然遏,任何比較大的贫途,放在ViewController里面比較臟的,只要不是Controller的核心邏輯待侵,都可以考慮拆出去丢早,然后在架構(gòu)的時候作為一個獨立模塊去定義,以及設(shè)計實現(xiàn)诫给。

  • 第二心法:拆分后的模塊要盡可能提高可復(fù)用性香拉,盡量做到DRY
    根據(jù)第一心法拆開來的東西,很有可能還是強業(yè)務(wù)相關(guān)的中狂,這種情況有的時候無法避免凫碌。但我們拆也要拆得好看,拆出來的部分最好能夠歸成某一類對象胃榕,然后最好能夠抽象出一個通用邏輯出來盛险,使他能夠復(fù)用。即使不能抽出通用邏輯勋又,那也盡量抽象出一個protocol苦掘,來實現(xiàn)IOP。

+第三心法: 要盡可能提高拆分模塊后的抽象度

也就是說楔壤,拆分的粒度要盡可能大一點鹤啡,封裝得要透明一些。提高抽象度事實上就是增加封裝的力度蹲嚣,將一個負(fù)責(zé)的業(yè)務(wù)抽象成只需要很少的輸入就能完成递瑰,就是高度抽象
提高抽象程度的好處在于祟牲,對于業(yè)務(wù)方來說,他只需要收集很少的信息(最小充要條件)抖部,做很少的調(diào)度(Controller負(fù)責(zé)大模塊調(diào)度说贝,大模塊里面再去做小模塊的調(diào)度),就能夠完成任務(wù)慎颗,這才是給Controller減負(fù)的正確姿勢乡恕。

如果拆分出來的模塊抽象程度不夠,模塊對外界要求的參數(shù)比較多俯萎,那么在Controller里面傲宜,關(guān)于收集參數(shù)的代碼就會多了很多。如果一部分參數(shù)的收集邏輯能夠由模塊來完成夫啊,那也可以做到幫Controller減輕負(fù)擔(dān)蛋哭。否則就感覺拆得不太干凈,因為Controller里面還是多了一些不必要的參數(shù)收集邏輯涮母。

如果拆分出來的粒度太小,Controller在完成任務(wù)的時候調(diào)度代碼要寫很多躁愿,那也不太好叛本。導(dǎo)致拆分粒度小的首要因素就是業(yè)務(wù)可能本身就比較復(fù)雜,拆分粒度小并不是不好彤钟,能大就大一點来候,如果小了,那也沒問題逸雹。針對這種情況的處理营搅,就需要采用strategy模式。

針對拆分粒度小的情況梆砸,我來舉個實際例子转质,這個例子來源于我的一個朋友他在做聊天應(yīng)用的消息發(fā)送模塊。當(dāng)消息是文字時帖世,直接發(fā)送休蟹。當(dāng)消息是圖片時,需要先向服務(wù)器申請上傳資源日矫,獲得資源ID之后再上傳圖片赂弓,上傳圖片完成之后拿到圖片URL,后面帶著URL再把信息發(fā)送出去哪轿。

這時候我們拆模塊盈魁,可以拆成:數(shù)據(jù)發(fā)送(叫A模塊),上傳資源申請(叫B模塊)窃诉,內(nèi)容上傳(叫C模塊)杨耙。那么要發(fā)送文字消息赤套,Controller調(diào)度A就可以了。如果要發(fā)送圖片消息按脚,Controller調(diào)度B->C->A于毙,假設(shè)將來還有上傳別的類型消息的任務(wù),他們又要依賴D/E/F模塊辅搬,那這個事情就很蛋疼唯沮,因為邏輯復(fù)雜了,Controller要調(diào)度的東西要區(qū)分的情況就多了堪遂,Controller就膨脹了介蛉。

那么怎么處理呢?可以采用Strategy模式溶褪。我們再來分析一下币旧,Controller要完成任務(wù),它初始情況下所具有的條件是什么猿妈?它有這條消息的所有數(shù)據(jù)吹菱,也知道這個消息的類型。那么它最終需要的是什么呢彭则?消息發(fā)送的結(jié)果:發(fā)送成功或失敗鳍刷。

<pre>
send msg
Controller ------------------> MessageSender
^ |
| |
| |
----------------------------------
success / fail
</pre>

上面就是我們要實現(xiàn)的最終結(jié)果,Controller只要把消息丟給MessageSender俯抖,然后讓MessageSender去做事情输瓜,做完了告訴Controller就好了。那么MessageSender里面怎么去調(diào)度邏輯芬萍?MessageSender里面可以有一個StrategyList尤揣,里面存放了表達各種邏輯的Block或者Invocation(Target-Action)。那么我們先定義一個Enum柬祠,里面規(guī)定了每種任務(wù)所需要的調(diào)度邏輯北戏。

<pre>
typedef NS_ENUM (NSUInteger, MessageSendStrategy)
{
MessageSendStrategyText = 0,
MessageSendStrategyImage = 1,
MessageSendStrategyVoice = 2,
MessageSendStrategyVideo = 3
}
</pre>

然后在MessageSender里面的StrategyList是這樣:

<pre>
@property (nonatomic, strong) NSArray *strategyList;

self.strategyList = @[TextSenderInvocation, ImageSenderInvocation, VoiceSenderInvocation, VideoSenderInvocation];

// 然后對外提供一個這樣的接口,同時有一個delegate用來回調(diào)

  • (void)sendMessage:(BaseMessage *)message withStrategy:(MessageSendStrategy)strategy;

@property (nonatomic, weak) id<MessageSenderDelegate> delegate;

@protocol MessageSenderDelegate<NSObject>

@required
- (void)messageSender:(MessageSender *)messageSender
didSuccessSendMessage:(BaseMessage *)message
strategy:(MessageSendStrategy)strategy;

  - (void)messageSender:(MessageSender *)messageSender
     didFailSendMessage:(BaseMessage *)message
               strategy:(MessageSendStrategy)strategy
                  error:(NSError *)error;

@end
</pre>

Controller里面是這樣使用的:

<pre>
[self.messageSender sendMessage:message withStrategy:MessageSendStrategyText];
</pre>

MessageSender里面是這樣的:
<pre>
[self.strategyList[strategy] invoke];
</pre>

然后在某個Invocation里面漫蛔,就是這樣的:
<pre>
[A invoke];
[B invoke];
[C invoke];
</pre>

這樣就好啦最欠,即便拆分粒度因為客觀原因無法細(xì)化,那也能把復(fù)雜的判斷邏輯和調(diào)度邏輯從Controller中抽出來惩猫,真正為Controller做到了減負(fù)芝硬。總之能夠做到大粒度就盡量大粒度轧房,實在做不到那也行拌阴,用Strategy把它hold住。這個例子是小粒度的情況奶镶,大粒度的情況太簡單迟赃,我就不舉了陪拘。

九、設(shè)計的心法

針對View層的架構(gòu)不光是看重如何合理地拆分MVC來給UIViewController減負(fù)纤壁,另外一點也要照顧到業(yè)務(wù)方的使用成本左刽。最好的情況是業(yè)務(wù)方什么都不知道,然后他把代碼放進去就能跑酌媒,同時還能獲得框架提供的種種功能欠痴。

  • 第一心法:盡可能減少繼承層級,涉及蘋果原生對象的盡量不要繼承
    繼承是罪惡秒咨,盡量不要繼承喇辽。雖然不可避免的是有些情況我們不得不從蘋果原生對象中繼承,比如UITableViewCell雨席。但我還是建議盡量不要通過繼承的方案來給原生對象添加功能菩咨,前面提到的Aspect方案和Category方案都可以使用。用Aspect+load來實現(xiàn)重載函數(shù)陡厘,用Category來實現(xiàn)添加函數(shù)抽米,當(dāng)然,耍點手段用Category來添加property也是沒問題的糙置。這些方案已經(jīng)覆蓋了繼承的全部功能缨硝,而且非常好維護,對于業(yè)務(wù)方也更加透明罢低,何樂而不為呢。還能再多額外兩個好處:1. 在業(yè)務(wù)方做業(yè)務(wù)開發(fā)或者做Demo時胖笛,可以脫離App環(huán)境网持,或花更少的時間搭建環(huán)境。2. 對業(yè)務(wù)方來說功能更加透明长踊,也符合業(yè)務(wù)方在開發(fā)時的第一直覺

  • 第二心法:做好代碼規(guī)范功舀,規(guī)定好代碼在文件中的布局,尤其是ViewController

  • 第三心法:能不放在Controller做的事情就盡量不要放在Controller里面去做

  • 第四心法:架構(gòu)師是為業(yè)務(wù)工程師服務(wù)的身弊,而不是去使喚業(yè)務(wù)工程師的

十辟汰、總結(jié)

其實針對View層的架構(gòu)設(shè)計,還是要做好三點:

  • 代碼規(guī)范
    制定良好的規(guī)范,代碼規(guī)范對于View層來說意義重大阱佛,畢竟View層非常重業(yè)務(wù)帖汞,如果代碼布局混亂,后來者很難接手凑术,也很難維護翩蘸。

  • 架構(gòu)模式
    選擇好合適的模式(MVC、MVCS淮逊、MVVM催首、VIPER)
    架構(gòu)模式具體如何選擇扶踊,完全取決于業(yè)務(wù)復(fù)雜度。如果業(yè)務(wù)相當(dāng)相當(dāng)復(fù)雜郎任,那就可以使用VIPER秧耗,如果相對簡單,那就直接MVC稍微改改就好了舶治。每一種已經(jīng)成為定式的架構(gòu)模式不見得都適合各自公司對應(yīng)的業(yè)務(wù)分井,所以需要各位架構(gòu)師根據(jù)情況去做一些拆分或者改變。拆分一般都不會出現(xiàn)問題歼疮,改變的時候杂抽,只要別把MVC三個角色搞混就好了,M該做啥做啥韩脏,C該做啥做啥缩麸,V該做啥做啥,不要亂來赡矢。關(guān)于大部分的架構(gòu)模式應(yīng)該是什么樣子杭朱,這篇文章里都已經(jīng)說過了,不過我認(rèn)為最重要的還是后面的心法吹散,模式只是招術(shù)弧械,熟悉了心法才能大巧不工。

  • 工具集
    根據(jù)業(yè)務(wù)情況針對ViewController做好拆分空民,提供一些小工具方便開發(fā)
    View層的工具集主要還是集中在如何對View進行布局刃唐,以及一些特定的View,比如帶搜索提示的搜索框這種界轩。這篇文章只提到了View布局的工具集画饥,其它的工具集相對而言是更加取決于各自公司的業(yè)務(wù)的,各自實現(xiàn)或者使用CocoaPods里現(xiàn)成的都不是很難浊猾。

架構(gòu)師要做針對View層的方案時抖甘,最好還是盡量遵守蘋果已有的規(guī)范和設(shè)計思想,然后根據(jù)自己過去開發(fā)iOS時的經(jīng)驗葫慎,盡可能給業(yè)務(wù)方在開發(fā)業(yè)務(wù)時減負(fù)衔彻,提高業(yè)務(wù)代碼的可維護性,就是View層架構(gòu)方案的最大目標(biāo)

對于小規(guī)耐蛋欤或者中等規(guī)模iOS開發(fā)團隊來說艰额,做好以上三點就足夠了。在大規(guī)模團隊中椒涯,有一個額外問題要考慮悴晰,就是跨業(yè)務(wù)頁面調(diào)用方案的設(shè)計。

十一、跨業(yè)務(wù)頁面調(diào)用方案的設(shè)計

跨業(yè)務(wù)頁面調(diào)用是指铡溪,當(dāng)一個App中存在A業(yè)務(wù)漂辐,B業(yè)務(wù)等多個業(yè)務(wù)時,B業(yè)務(wù)有可能會需要展示A業(yè)務(wù)的某個頁面棕硫,A業(yè)務(wù)也有可能會調(diào)用其他業(yè)務(wù)的某個頁面髓涯。在小規(guī)模的App中,我們直接import其他業(yè)務(wù)的某個ViewController然后或者push或者present哈扮,是不會產(chǎn)生特別大的問題的纬纪。但是如果App的規(guī)模非常大,涉及業(yè)務(wù)數(shù)量非常多滑肉,再這么直接import就會出現(xiàn)問題包各。

<pre>
-------------- -------------- --------------
| | page call | | page call | |
| Buisness A | <---------> | Buisness B | <---------> | Buisness C |
| | | | | |
-------------- -------------- --------------
\ | /
\ | /
\ | /
\ | /
\ | /
--------------------------------
| |
| App |
| |
--------------------------------
</pre>

可以看出,跨業(yè)務(wù)的頁面調(diào)用在多業(yè)務(wù)組成的App中會導(dǎo)致橫向依賴靶庙。那么像這樣的橫向依賴问畅,如果不去設(shè)法解決,會導(dǎo)致什么樣的結(jié)果六荒?

1.當(dāng)一個需求需要多業(yè)務(wù)合作開發(fā)時护姆,如果直接依賴,會導(dǎo)致某些依賴層上端的業(yè)務(wù)工程師在前期空轉(zhuǎn)掏击,依賴層下端的工程師任務(wù)繁重卵皂,而整個需求完成的速度會變慢,影響的是團隊開發(fā)迭代速度砚亭。

2.當(dāng)要開辟一個新業(yè)務(wù)時灯变,如果已有各業(yè)務(wù)間直接依賴,新業(yè)務(wù)又依賴某個舊業(yè)務(wù)捅膘,就導(dǎo)致新業(yè)務(wù)的開發(fā)環(huán)境搭建困難添祸,因為必須要把所有相關(guān)業(yè)務(wù)都塞入開發(fā)環(huán)境,新業(yè)務(wù)才能進行開發(fā)篓跛。影響的是新業(yè)務(wù)的響應(yīng)速度。

3.當(dāng)某一個被其他業(yè)務(wù)依賴的頁面有所修改時坦刀,比如改名愧沟,涉及到的修改面就會特別大。影響的是造成任務(wù)量和維護成本都上升的結(jié)果鲤遥。

當(dāng)然沐寺,如果App規(guī)模特別小,這三點帶來的影響也會特別小盖奈,但是在阿里這樣大規(guī)模的團隊中混坞,像天貓/淘寶這樣大規(guī)模的App,一旦遇上這里面哪怕其中一件事情,就特么很坑爹究孕。

那么應(yīng)該怎樣處理這個問題啥酱?
讓依賴關(guān)系下沉。

怎么讓依賴關(guān)系下沉厨诸?引入Mediator模式镶殷。

所謂引入Mediator模式來讓依賴關(guān)系下沉,實質(zhì)上就是每次呼喚頁面的時候微酬,通過一個中間人來召喚另外一個頁面绘趋,這樣只要每個業(yè)務(wù)依賴這個中間人就可以了,中間人的角色就可以放在業(yè)務(wù)層的下面一層颗管,這就是依賴關(guān)系下沉陷遮。

<pre>
-------------- -------------- --------------
| | | | | |
| Buisness A | | Buisness B | | Buisness C |
| | | | | |
-------------- -------------- --------------
\ | /
\ | /
\ | / 通過Mediater來召喚頁面
\ | /
\ | /
--------------------------------
| |
| Mediater |
| |
--------------------------------
|
|
|
|
|
--------------------------------
| |
| App |
| |
--------------------------------
</pre>

當(dāng)A業(yè)務(wù)需要調(diào)用B業(yè)務(wù)的某個頁面的時候,將請求交給Mediater垦江,然后由Mediater通過某種手段獲取到B業(yè)務(wù)頁面的實例帽馋,交還給A就行了。在具體實現(xiàn)這個機制的過程中疫粥,有以下幾個問題需要解決:

  1. 設(shè)計一套通用的請求機制茬斧,請求機制需要跟業(yè)務(wù)剝離,使得不同業(yè)務(wù)的頁面請求都能夠被Mediater處理
  2. 設(shè)計Mediater根據(jù)請求如何獲取其他業(yè)務(wù)的機制梗逮,Mediater需要知道如何處理請求项秉,上哪兒去找到需要的頁面
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市慷彤,隨后出現(xiàn)的幾起案子娄蔼,更是在濱河造成了極大的恐慌,老刑警劉巖底哗,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岁诉,死亡現(xiàn)場離奇詭異,居然都是意外死亡跋选,警方通過查閱死者的電腦和手機涕癣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來前标,“玉大人坠韩,你說我怎么就攤上這事×读校” “怎么了只搁?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俭尖。 經(jīng)常有香客問我氢惋,道長洞翩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任焰望,我火速辦了婚禮骚亿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柿估。我一直安慰自己循未,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布秫舌。 她就那樣靜靜地躺著的妖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪足陨。 梳的紋絲不亂的頭發(fā)上嫂粟,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音墨缘,去河邊找鬼星虹。 笑死,一個胖子當(dāng)著我的面吹牛镊讼,可吹牛的內(nèi)容都是我干的宽涌。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蝶棋,長吁一口氣:“原來是場噩夢啊……” “哼卸亮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起玩裙,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤兼贸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吃溅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溶诞,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年决侈,在試婚紗的時候發(fā)現(xiàn)自己被綠了螺垢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡赖歌,死狀恐怖枉圃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俏站,我是刑警寧澤讯蒲,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布痊土,位于F島的核電站肄扎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜犯祠,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一旭等、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衡载,春花似錦搔耕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梨睁,卻和暖如春鲸睛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坡贺。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工官辈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遍坟。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓拳亿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親愿伴。 傳聞我的和親對象是個殘疾皇子肺魁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,851評論 2 361

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