iOS 客戶端架構(gòu)設(shè)計(jì)

轉(zhuǎn)載自:藍(lán)晨鈺的博客

猿題庫(kù)是一個(gè)擁有數(shù)千萬(wàn)用戶的創(chuàng)業(yè)公司,從2013年題庫(kù)項(xiàng)目起步到2015年,團(tuán)隊(duì)保持了極高的生產(chǎn)效率,使我們的產(chǎn)品完成了五個(gè)大版本和數(shù)十個(gè)小版本的高速迭代。在如此快速的開(kāi)發(fā)過(guò)程中,如何保證代碼的質(zhì)量,降低后期維護(hù)的成本,以及為項(xiàng)目越來(lái)越快的版本迭代速度提供支持,成為了我們關(guān)注的重要問(wèn)題。這篇文章將闡明我們?cè)谠愁}庫(kù) iOS 客戶端的架構(gòu)設(shè)計(jì)。

MVC

MVC,Model-View-Controller,我們從這個(gè)古老而經(jīng)典的設(shè)計(jì)模式入手。采用 MVC 這個(gè)架構(gòu)的最大的優(yōu)點(diǎn)在于其概念簡(jiǎn)單,易于理解,幾乎任何一個(gè)程序員都會(huì)有所了解,幾乎每一所計(jì)算機(jī)院校都教過(guò)相關(guān)的知識(shí)。而在 iOS 客戶端開(kāi)發(fā)中,MVC 作為官方推薦的主流架構(gòu),不但 SDK 已經(jīng)為我們實(shí)現(xiàn)好了 UIView、UIViewController 等相關(guān)的組件,更是有大量的文檔和范例供我們參考學(xué)習(xí),可以說(shuō)是一種非常通用而成熟的架構(gòu)設(shè)計(jì)。

但 MVC 也有他的壞處。由于 MVC 的概念過(guò)于簡(jiǎn)單樸素,已經(jīng)越來(lái)越難以適應(yīng)如今客戶端的需求,大量的代碼邏輯在 MVC 中并沒(méi)有定義得很清楚究竟應(yīng)該放在什么地方,導(dǎo)致他們很容易就會(huì)堆積在 Controller 里,成為了人們所說(shuō)的 Massive View Controller。

MVVM

MVVM,Model-View-ViewModel,一個(gè)從 MVC 模式中進(jìn)化而來(lái)的設(shè)計(jì)模式,最早于2005年被微軟的 WPF 和 Silverlight 的架構(gòu)師 John Gossman 提出。在 iOS 開(kāi)發(fā)中實(shí)踐 MVVM 的話,通常會(huì)把大量原來(lái)放在 ViewController 里的視圖邏輯和數(shù)據(jù)邏輯移到 ViewModel 里,從而有效的減輕了 ViewController 的負(fù)擔(dān)。另外通過(guò)分離出來(lái)的 ViewModel 獲得了更好的測(cè)試性,我們可以針對(duì) ViewModel 來(lái)測(cè)試,解決了界面元素難于測(cè)試的問(wèn)題。MVVM 通常還會(huì)和一個(gè)強(qiáng)大的綁定機(jī)制一同工作,一旦 ViewModel 所對(duì)應(yīng)的 Model 發(fā)生變化時(shí),ViewModel 的屬性也會(huì)發(fā)生變化,而相對(duì)應(yīng)的 View 也隨即產(chǎn)生變化。

同樣的,MVVM 也有他的缺點(diǎn):

一個(gè)首要的缺點(diǎn)是,MVVM 的學(xué)習(xí)成本和開(kāi)發(fā)成本都很高。MVVM 是一個(gè)年輕的設(shè)計(jì)模式,大多數(shù)人對(duì)他的了解都不如 MVC 熟悉,基于綁定機(jī)制來(lái)進(jìn)行編程需要一定的學(xué)習(xí)才能較好的上手。同時(shí)在 iOS 客戶端開(kāi)發(fā)中,并沒(méi)有現(xiàn)成的綁定機(jī)制可以使用,要么使用 KVO,要么引入類似 ReactiveCocoa 這樣的第三方庫(kù),使得學(xué)習(xí)成本和開(kāi)發(fā)成本進(jìn)一步提高。

另一個(gè)缺點(diǎn)是,數(shù)據(jù)綁定使 Debug 變得更難了。數(shù)據(jù)綁定使程序異常能快速的傳遞到其他位置,在界面上發(fā)現(xiàn)的 Bug 有可能是由 ViewModel 造成的,也有可能是由 Model 層造成的,傳遞鏈越長(zhǎng),對(duì) Bug 的定位就越困難。

同時(shí)還必須指出的是,在傳統(tǒng)的 MVVM 架構(gòu)中,ViewModel 依然承載的大量的邏輯,包括業(yè)務(wù)邏輯,界面邏輯,數(shù)據(jù)存儲(chǔ)和網(wǎng)絡(luò)相關(guān),使得 ViewModel 仍然有可能變得和 MVC 中 ViewController 一樣臃腫。

在兩種架構(gòu)中權(quán)衡而產(chǎn)生的架構(gòu)

兩種架構(gòu)的優(yōu)點(diǎn)都想要,缺點(diǎn)又都想避開(kāi),我們?cè)趦煞N架構(gòu)中權(quán)衡了他們的優(yōu)缺點(diǎn),設(shè)計(jì)出了一個(gè)新的架構(gòu),起了一個(gè)名字叫:MVVM without Binding with DataController,架構(gòu)圖如下:

ViewModel

先來(lái)看右邊視圖相關(guān)的部分,傳統(tǒng)的 MVC 當(dāng)中 ViewController 中有大量的數(shù)據(jù)展示和樣式定制的邏輯,我們引入 MVVM 中 ViewModel 的概念,將這部分視圖邏輯移到了 ViewModel 當(dāng)中。在這個(gè)設(shè)計(jì)中,每一個(gè) View 都會(huì)有一個(gè)對(duì)應(yīng)的 ViewModel,其包含了這個(gè) View 數(shù)據(jù)展示和樣式定制所需要的所有數(shù)據(jù)。同時(shí),我們不引入雙向綁定機(jī)制或者觀察機(jī)制,而是通過(guò)傳統(tǒng)的代理回調(diào)或是通知來(lái)將 UI 事件傳遞給外界。而 ViewController 只需要生成一個(gè) ViewModel 并把這個(gè)裝配給對(duì)應(yīng)的 View,并接受相應(yīng)的 UI 事件即可。

這樣做有幾個(gè)好處:首先是 View 的完全解耦合,對(duì)于 View 來(lái)說(shuō),只需要確定好相應(yīng)的 ViewModel 和 UI 事件的回調(diào)接口即可與 Model 層完全隔離;而 ViewController 可以避免與 View 的具體表現(xiàn)打交道,這部分職責(zé)被轉(zhuǎn)交給了 ViewModel,有效的減輕了 ViewController 的負(fù)擔(dān);同時(shí)我們棄用了傳統(tǒng)綁定機(jī)制,使用了傳統(tǒng)的易于理解的回調(diào)機(jī)制來(lái)傳遞 UI 事件,降低了學(xué)習(xí)成本,同時(shí)使得數(shù)據(jù)的流入和流出變得易于觀察和控制,降低了維護(hù)了調(diào)適的成本。

DataController

接下來(lái)我們關(guān)注 Model 和 VC 之間的關(guān)系。如之前提到,在傳統(tǒng)的 MVVM 中,ViewModel 接管了 ViewController 的大部分職責(zé),包括數(shù)據(jù)獲取,處理,加工等等,導(dǎo)致其很有可能變得臃腫。我們將這部分邏輯抽離出來(lái),引入一個(gè)新的部件,DataController。

ViewController 可以向 DataController 請(qǐng)求獲取或是操作數(shù)據(jù),也可以將一些事件傳遞給 DataController,這些事件可以是 UI 事件觸發(fā)的。DataController 在收到這些請(qǐng)求后,再向 Model 層獲取或是更新數(shù)據(jù),最后再將得到的數(shù)據(jù)加工成 ViewController 最終需要的數(shù)據(jù)返回。

這樣做之后,使得數(shù)據(jù)相關(guān)的邏輯解耦合,數(shù)據(jù)的獲取、修改、加工都放在 Data Controller 中處理,View Controller 不關(guān)心數(shù)據(jù)如何獲得,如何處理,Data Controller 也不關(guān)心界面如何展示,如何交互。同時(shí) Data Controller 因?yàn)橥耆徒缑鏌o(wú)關(guān),所以可以有更好的測(cè)試性和復(fù)用性。

DataController 層和 Model 層之間的界限并不是僵硬的,但需要保證每一個(gè) ViewController 都有一個(gè)對(duì)應(yīng)的 DataController。Data Controller 更強(qiáng)調(diào)的是其作為業(yè)務(wù)邏輯對(duì)外的接口。而在 DataController 中調(diào)用更底層的 Model 層邏輯是我們推薦的編程范式,例如數(shù)據(jù)加工層,網(wǎng)絡(luò)層,持久層等。

在后面的例子中,我們會(huì)更詳細(xì)的講解 DataController 的實(shí)現(xiàn)細(xì)節(jié)。

Show me the code

我們以猿題庫(kù)主頁(yè)為例,展示我們是如何使用應(yīng)用這個(gè)架構(gòu)的。

主頁(yè)有幾個(gè)部分組成,最上面的小猴子 Banner 頁(yè),用于滾動(dòng)展示一些活動(dòng)信息;中間有一個(gè)用戶名字的頁(yè)面,用于展示用戶信息和答題情況以及一些心靈雞湯;最底下的這部分是一個(gè)課目選擇頁(yè)面,展示了用戶開(kāi)啟的科目入口,在更多選項(xiàng)里面可以進(jìn)一步配置這些科目入口。接下來(lái)我們會(huì)以科目頁(yè)面(SubjectView)為例展示一些細(xì)節(jié)。

ViewController

我們會(huì)給每一個(gè) ViewController 都創(chuàng)建一個(gè)對(duì)應(yīng)的 DataController。 例如我們給主頁(yè)建一個(gè)類起名叫APEHomePraticeViewController,同時(shí)他會(huì)有一個(gè)對(duì)應(yīng)的 DataController 起名叫APEHomePraticeDataController。同時(shí)我們把頁(yè)面拆分為幾個(gè)部分,每個(gè)部分有一個(gè)相對(duì)應(yīng)的 SubView。代碼如下:

@interface?APEHomePracticeViewController?()

@property?(nonatomic,?strong,?nullable)?UIScrollView?*contentView;

@property?(nonatomic,?strong,?nullable)?APEHomePracticeBannerView?*bannerView;

@property?(nonatomic,?strong,?nullable)?APEHomePracticeActivityView?*activityView;

@property?(nonatomic,?strong,?nullable)?APEHomePracticeSubjectsView?*subjectsView;

@property?(nonatomic,?strong,?nullable)?APEHomePracticeDataController?*dataController;

@end

在viewDidLoad的時(shí)候,初始化好各個(gè) SubView,并設(shè)置好布局:

-?(void)setupContentView?{

self.contentView?=?[[UIScrollView?alloc]?init];

[self.view?addSubview:self.contentView];

self.bannerView?=?[[APEHomePracticeBannerView?alloc]?init];

self.activityView?=?[[APEHomePracticeActivityView?alloc]?init];

self.subjectsView?=?[[APEHomePracticeSubjectsView?alloc]?init];

self.subjectsView.delegate?=?self;

[self.contentView?addSubview:self.bannerView];

[self.contentView?addSubview:self.activityView];

[self.contentView?addSubview:self.subjectsView];

//?Layout?Views?...

}

接下來(lái),ViewController 會(huì)向 DataController 請(qǐng)求 Subject 相關(guān)的數(shù)據(jù),并在請(qǐng)求完成后,用獲得的數(shù)據(jù)生成 ViewModel,將其裝配給 SubjectView,完成界面渲染,代碼如下:

-?(void)fetchSubjectData?{

[self.dataController?requestSubjectDataWithCallback:^(NSError?*error)?{

if(error?==?nil)?{

[self?renderSubjectView];

}

-?(void)renderSubjectView?{

APEHomePracticeSubjectsViewModel?*viewModel?=

[APEHomePracticeSubjectsViewModel?viewModelWithSubjects:self.dataController.openSubjects];

[self.subjectsView?bindDataWithViewModel:viewModel];

}

數(shù)據(jù)結(jié)構(gòu)

為了更好的演示,我們接下來(lái)要介紹一下 Subject 相關(guān)的數(shù)據(jù)結(jié)構(gòu):

APESubject是科目的資源結(jié)構(gòu),包含了 Subject 的 id 和 name 等資源屬性,這部分屬性是用戶無(wú)關(guān)的;APEUserSubject是用戶的科目信息,包含了用戶是否打開(kāi)某個(gè)學(xué)科的屬性。

@interface?APESubject?:?NSObject

@property?(nonatomic,?strong,?nullable)?NSNumber?*id;

@property?(nonatomic,?strong,?nullable)?NSString?*name;

@end

@interface?APEUserSubject?:?NSObject

@property?(nonatomic,?strong,?nullable)?NSNumber?*id;

@property?(nonatomic,?strong,?nullable)?NSNumber?*updatedTime;

///??On?or?Off

@property?(nonatomic)?APEUserSubjectStatus?status;

@end

DataController

如我們之前所說(shuō),每一個(gè) ViewController 都會(huì)有一個(gè)對(duì)應(yīng)的 DataController,這一類 DataController 的主要職責(zé)是處理這個(gè)頁(yè)面上的所有數(shù)據(jù)相關(guān)的邏輯,我們稱其為 View Related Data Controller。

//?APEHomePracticeDataController.h

@interface?APEHomePracticeDataController?:?APEBaseDataController

//?1

@property?(nonatomic,?strong,?nonnull,?readonly)?NSArray?*openSubjects;

//?2

-?(void)requestSubjectDataWithCallback:(nonnull?APECompletionCallback)callback;

@end

上面的這個(gè)代碼

我們定義了一個(gè)界面最終需要的數(shù)據(jù)的 property,這里是openSubjects,這個(gè) property 會(huì)存儲(chǔ)用戶打開(kāi)的科目列表,他的類型是APESubject。

我們還會(huì)定義一個(gè)接口來(lái)請(qǐng)求 openSubject 數(shù)據(jù)。

DataController 這一層是一個(gè)靈活性很高的部件,一個(gè) DataController 可以復(fù)用更小的 DataController,這一類更小的 DataController 通常只會(huì)包含純粹的或是更抽象的 Model 相關(guān)的邏輯,例如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫(kù)請(qǐng)求,或是數(shù)據(jù)加工等。我們稱這一類 DataController 為 Model Related Data Controller。

Model Related Data Controller 通常會(huì)為上層提供正交的數(shù)據(jù):

//?APEHomePracticeDataController.m

@interface?APEHomePracticeDataController?()

@property?(nonatomic,?strong,?nonnull)?APESubjectDataController?*subjectDataController;

@end

@implementation?APEHomePracticeDataController

-?(void)requestSubjectDataWithCallback:(nonnull?APECompletionCallback)callback?{

APEDataCallback?dataCallback?=?^(NSError?*error,?id?data)?{

callback(error);

};

[self.subjectDataController?requestAllSubjectsWithCallback:dataCallback];

[self.subjectDataController?requestUserSubjectsWithCallback:dataCallback];

}

-?(nonnull?NSArray?*)openSubjects?{

returnself.subjectDataController.openSubjectsWithCurrentPhase??:?@[];

}

@end

在我們的APEHomePraticeDataController的實(shí)現(xiàn)中,就包含了一個(gè)APESubjectDataController,這個(gè)subjectDataController會(huì)負(fù)責(zé)請(qǐng)求 All Subjects 和 User Subjects,并將其加工成上層所最終需要的 Open Subjects。(備注:這個(gè)例子里面的 callback 會(huì)回調(diào)多次是猿題庫(kù)產(chǎn)品的需求,如有需要,可在這一層控制請(qǐng)求都完成后再調(diào)用上層回調(diào))

事實(shí)上,Model Related Data Controller 可以一般性的認(rèn)為就是大家經(jīng)常在寫(xiě)的 Model 層代碼,例如 UserAgent,UserService,PostService 之類的服務(wù)。之后讀者若想重構(gòu)就項(xiàng)目成這個(gè)架構(gòu),大可以不必糾結(jié)于形式,直接在 DataController 里調(diào)用舊有代碼的邏輯即可,如圖下面這樣的行為都是允許的:

ViewModel

每一個(gè) View 都會(huì)有一個(gè)對(duì)應(yīng)的 ViewModel,這個(gè) ViewModel 會(huì)包含展示這個(gè) View 所需要的所有數(shù)據(jù)。

我們會(huì)使用工廠方法來(lái)創(chuàng)建 View Model,例如這個(gè)例子里,Subject View Model 不需要關(guān)心傳遞給他是什么樣的 Subject,所有的課目或者只是用戶開(kāi)啟的科目。

@interface?APEHomePracticeSubjectsViewModel?:?NSObject

@property?(nonatomic,?strong,?nonnull)?NSArray*cellViewModels;

@property?(nonatomic,?strong,?nonnull)?UIColor?*backgroundColor;

+?(nonnull?APEHomePracticeSubjectsViewModel?*)viewModelWithSubjects:(nonnull?NSArray?*)subjects;

@end

ViewModel 可以包含更小的 ViewModel,就像 View 可以有 SubView 一樣。SubjectView 的內(nèi)部是由一個(gè)UICollectionView實(shí)現(xiàn)的,所以我們也給了對(duì)應(yīng)的 Cell 設(shè)計(jì)了一個(gè) ViewModel。

需要額外注意的是,ViewModel 一般來(lái)說(shuō)會(huì)包含的顯示界面所需要的所有元素,但粒度是可以控制。一般來(lái)說(shuō),我們只把會(huì)因?yàn)闃I(yè)務(wù)變化而變化的部分設(shè)為 ViewModel 的一部分,例如這里的 titleColor 和 backgroundColor 會(huì)因?yàn)橹黝}不同而變化,但字體的大小(titleFont)卻是不會(huì)變的,所以不需要事無(wú)巨細(xì)的都加到 ViewModel 里。

@interface?APEHomePracticeSubjectsCollectionCellViewModel?:?NSObject

@property?(nonatomic,?strong,?nonnull)?UIImage?*image;

@property?(nonatomic,?strong,?nonnull)?UIImage?*highlightedImage;

@property?(nonatomic,?strong,?nonnull)?NSString?*title;

@property?(nonatomic,?strong,?nonnull)?UIColor?*titleColor;

@property?(nonatomic,?strong,?nonnull)?UIColor?*backgroundColor;

+?(nonnull?APEHomePracticeSubjectsCollectionCellViewModel?*)viewModelWithSubject:(nonnull

APESubject?*)subject;

+?(nonnull?APEHomePracticeSubjectsCollectionCellViewModel?*)viewModelForMore;

@end

View

View 只需要定義好裝配 ViewModel 的接口和定義好 UI 回調(diào)事件即可:

@protocol?APEHomePracticeSubjectsViewDelegate?-?(void)homePracticeSubjectsView:(nonnull?APEHomePracticeSubjectsView?*)subjectView

didPressItemAtIndex:(NSInteger)index;

@end

@interface?APEHomePracticeSubjectsView?:?UIView

@property?(nonatomic,?strong,?nullable,?readonly)?APEHomePracticeSubjectsViewModel?*viewModel;

@property?(nonatomic,?weak,?nullable)?id?delegate;

-?(void)bindDataWithViewModel:(nonnull?APEHomePracticeSubjectsViewModel?*)viewModel;

@end

渲染界面的時(shí)候,完全依靠 ViewModel 進(jìn)行,包括 View 的 SubView 也會(huì)使用 ViewModel 里面的子 ViewModel 渲染。

-?(void)bindDataWithViewModel:(nonnull?APEHomePracticeSubjectsViewModel?*)viewModel?{

self.viewModel?=?viewModel;

self.backgroundColor?=?viewModel.backgroundColor;

[self.collectionView?reloadData];

[self?setNeedsUpdateConstraints];

}

-?(UICollectionViewCell?*)collectionView:(UICollectionView?*)collectionView?cellForItemAtIndexPath:

(NSIndexPath?*)indexPath?{

APEHomePracticeSubjectsCollectionViewCell?*cell?=?[collectionView

dequeueReusableCellWithReuseIdentifier:@"Cell"forIndexPath:indexPath];

if(0?<=?indexPath.row?&&?indexPath.row?<?self.viewModel.cellViewModels.count)?{

APEHomePracticeSubjectsCollectionCellViewModel?*vm?=

self.viewModel.cellViewModels[indexPath.row];

[cell?bindDataWithViewModel:vm];

}

returncell;

}

至此,我們就完成了所有的步驟。我們回過(guò)頭再看一下 ViewController 的職責(zé)就回變的非常簡(jiǎn)單,裝配好 View,向 DataController 請(qǐng)求數(shù)據(jù),裝配 ViewModel,配置給 View,接收 View 的UI事,一切復(fù)雜的操作都能夠的代理出去。

總結(jié)

優(yōu)點(diǎn)

通過(guò)上面的例子我們可以看到,這個(gè)架構(gòu)有幾個(gè)優(yōu)點(diǎn):

層次清晰,職責(zé)明確:和界面有關(guān)的邏輯完全劃到 ViewModel 和 View 一遍,其中 ViewModel 負(fù)責(zé)界面相關(guān)邏輯,View 負(fù)責(zé)繪制;Data Controller 負(fù)責(zé)頁(yè)面相關(guān)的數(shù)據(jù)邏輯,而 Model 還是負(fù)責(zé)純粹的數(shù)據(jù)層邏輯。 ViewController 僅僅只是充當(dāng)簡(jiǎn)單的膠水作用。

耦合度低,測(cè)試性高:除開(kāi) ViewController 外,各個(gè)部件可以說(shuō)是完全解耦合的,各個(gè)部分也是可以完全獨(dú)立測(cè)試的。同一個(gè)功能,可以分別由不同的開(kāi)發(fā)人員分別進(jìn)行開(kāi)發(fā)界面和邏輯,只需要確立好接口即可。

復(fù)用性高:解耦合帶來(lái)的額外好處就是復(fù)用性高,例如同一個(gè)View,只需要多一個(gè)工廠方法生成 ViewModel,就可以直接復(fù)用。數(shù)據(jù)邏輯代碼不放在 ViewController 層也可以更方便的復(fù)用。

學(xué)習(xí)成本低: 本質(zhì)上來(lái)說(shuō),這個(gè)架構(gòu)屬于對(duì) MVC 的優(yōu)化,主要在于解決 Massive View Controller 問(wèn)題,把原本屬于 View Controller 的職責(zé)根據(jù)界面和邏輯部分相應(yīng)的拆到 ViewModel 和 DataController 當(dāng)中,所以是一個(gè)非常易于理解的架構(gòu)設(shè)計(jì),即使是新手也可以很快上手。

開(kāi)發(fā)成本低: 完全不需要引入任何第三方庫(kù)就可以進(jìn)行開(kāi)發(fā),也避免了因?yàn)?MVVM 維護(hù)成本高的問(wèn)題。

實(shí)施性高,重構(gòu)成本低:可以在 MVC 架構(gòu)上逐步重構(gòu)的架構(gòu),不需要整體重寫(xiě),是一種和 MVC 兼容的設(shè)計(jì)。

缺點(diǎn)

不可否認(rèn)的是,這個(gè)設(shè)計(jì)也有其相應(yīng)的缺點(diǎn),由于其把傳統(tǒng) MVVM 里面的 VM 拆成兩部分,會(huì)照成下面的一些情況:

當(dāng)頁(yè)面的交互邏輯非常多時(shí),需要頻繁的在 DC-VC-VM 里來(lái)回傳遞信息,造成了大量膠水代碼。

另外,由于在傳統(tǒng)的 MVVM 中 VM 原本是一體的,一些復(fù)雜的交互本來(lái)可以在 VM 中直接完成測(cè)試,如今卻需要同時(shí)使用 DC 和 VM 并附上一些膠水代碼才能進(jìn)行測(cè)試。

沒(méi)有了 Binding,代碼寫(xiě)起來(lái)會(huì)更費(fèi)勁一點(diǎn)(仁者見(jiàn)仁,智者見(jiàn)智)。

后記

MVVM 是一個(gè)很棒的架構(gòu),私底下我也會(huì)用其來(lái)做一些個(gè)人項(xiàng)目,但在公司項(xiàng)目里,我會(huì)更慎重的考慮個(gè)中利弊。我做這個(gè)設(shè)計(jì)的時(shí)候,心儀 MVVM 的種種好處,又忌憚?dòng)谒姆N種壞處,再考慮到團(tuán)隊(duì)的開(kāi)發(fā)和維護(hù)成本,所以最終設(shè)計(jì)成了如今這樣。

個(gè)人認(rèn)為,好的架構(gòu)設(shè)計(jì)的都是和團(tuán)隊(duì)以及業(yè)務(wù)場(chǎng)景息息相關(guān)的。我們這套架構(gòu)幫助我們解決了 ViewController 代碼堆積的問(wèn)題,也帶來(lái)了更清晰明了的代碼層級(jí)和模塊職責(zé),同時(shí)沒(méi)有引入過(guò)多的復(fù)雜性。希望大家也能充分理解這套架構(gòu)的適用場(chǎng)景,在自己的 APP 架構(gòu)設(shè)計(jì)中有所借鑒。

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

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