項目背景:公司的產(chǎn)品主要是在*的多個國家泰讽,在經(jīng)歷C輪融資后,準備加快步伐占領(lǐng)市場。公司決定分多個APP(以前是一個APP)已卸,同時拓展新的業(yè)務佛玄,需要開發(fā)新的APP,目前團隊人數(shù)20+累澡,人數(shù)還在不斷的增加梦抢,計劃35左右。分成7-10 team,為了解決多人開發(fā)的麻煩愧哟,加快開發(fā)速度奥吩,在公司只有5人的時候,我們一致同意組件化開發(fā)蕊梧,哈哈哈哈哈霞赫,當時的決定感覺好搞笑,記得有次和朋友(大廠)聊天肥矢,向他取經(jīng)端衰,和他說我們5人在組件化開發(fā),他說我們在裝B甘改。在目前看來旅东,當時的決定真的很正確,為開發(fā)十艾、測試節(jié)省了很多的時間
組件化定義
首先需要對組件進行定義玉锌,叫組件也好,模塊也罷疟羹,我們姑且認為我們討論的范疇是【獨立的業(yè)務或者功能單位】主守。至于這個單位的粒度大小,
需要工程師自己把握榄融。當我們寫一個類的時候参淫,我們會謹記高內(nèi)聚,低耦合的原則去設(shè)計這個類愧杯,當涉及多個類之間交互的時候涎才,
我們也會運用SOLID原則,或者已有的設(shè)計模式去優(yōu)化設(shè)計力九,但在實現(xiàn)完整的業(yè)務模塊的時候耍铜,我們很容易忘記對這個模塊去做設(shè)計上的思考,
粒度越大跌前,越難做出精細穩(wěn)定的設(shè)計棕兼,我暫且把這個粒度認為是組件的粒度。組件是由一個或多個類構(gòu)成抵乓,能完整描述一個業(yè)務場景伴挚,
并能被其他業(yè)務場景復用的功能單位靶衍。組件就像是PC時代個人組裝電腦時購買的一個個部件,比如內(nèi)存茎芋,硬盤颅眶,CPU,顯示器等田弥,
拿出其中任何一個部件都能被其他的PC所使用涛酗。
組件化是一個廣泛的定義,都是開發(fā)人員根據(jù)自己在開發(fā)經(jīng)驗中總結(jié)出來的偷厦。我的想法:在高內(nèi)聚煤杀,低耦合的設(shè)計原則的基礎(chǔ)上,去分割項目沪哺,通過分層的思想去封裝功能單元沈自,每一個單元都是獨立的,在不影響其他功能的同時辜妓,可以單獨測試枯途。說的更明白的,可以把蘋果手機(4 -9)系列類比成項目A籍滴,iPhone X 系列類比成項目B酪夷,Mac系列類比成項目C,那么孽惰,手機(4-9)我們可以類比成帶UI的模塊晚岭,每一款的手機都有自己的特性;蘋果手機的充電線可以在項目A勋功、B坦报、C使用,蘋果耳機可以在項目A狂鞋、B使用片择,這類二級充電線可以類比成不具備業(yè)務場景的功能模塊。目前我們的項目主要將組建分為以下幾類:
帶UI屬性的獨立業(yè)務模塊
帶UI屬性的業(yè)務組件
不具備UI屬性的獨立業(yè)務組件
不具備業(yè)務場景的功能組件
帶UI屬性的獨立業(yè)務模塊
這類我一般稱之為模塊骚揍,是較大的獨立業(yè)務字管,具有很強的業(yè)務性,模塊之間是不相互引用的信不,不然就失去了組件化的意義嘲叔。比如一個APP的首頁模塊、登錄模塊抽活、我的模塊硫戈、購物車模塊等等。一般情況可以是tabbarController的item,當然也可以是其他的酌壕,沒有強制規(guī)定掏愁。具體看項目的拆分以及人員的安排。這類模塊一般都有一個跳轉(zhuǎn)的入口卵牍,可以是itme可以是push果港、可以是Present的方式;其次這類模塊一般都有一系列的操作糊昙,如:購物車辛掠,選商品、下單释牺、分期等萝衩;登錄模塊的忘記手機號碼、忘記密碼等等一系列操作没咙。這里又設(shè)計到模塊之間的跳轉(zhuǎn)猩谊,這也是組件化的核心之一,既然模塊之間是相互獨立的祭刚,前面也說了牌捷,模塊之間是不相互引用的,那么怎么跳轉(zhuǎn)呢涡驮?可以通過URL方式暗甥,蘑菇街 就是通過這樣的方式,目前我們的項目用的是Mediator,還有協(xié)議的方式捉捅。
帶UI屬性的公共組件
這類組件涉及到UI業(yè)務功能撤防,不是很常見。剛好我在項目中遇到過棒口,恰巧是我做的寄月。有一個涉及到UI的功能,需要在A无牵、B剥懒、C三個模塊中使用,于是毫不猶豫地封裝成組件合敦,這類組件是最復雜初橘、最麻煩的。帶UI屬性的獨立業(yè)務模塊我們可以在模塊內(nèi)"為所欲為"反正都是自己的功能充岛,別人也管不到保檐,但是帶UI屬性的業(yè)務組建,不只是你在使用崔梗,涉及到其他的組員夜只,要考慮到其他功能的特性∷馄牵總之這樣的組件扔亥,即抽象又具體场躯,我只能這樣形容
#######不具備UI屬性的獨立業(yè)務組件
這類組件是比較常見的,它不和任何的UI相關(guān)旅挤,但是和具體的功能相關(guān)踢关,如:分享功能,第三方登錄功能等我們只要接口的返回的數(shù)據(jù)粘茄,不需要我們push或者Present,只需要一個簡單的接口签舞,內(nèi)部怎么實現(xiàn)的不在考慮范圍內(nèi)。類似我們調(diào)用蘋果給我們封裝好的接口柒瓣。如 let button = UIButton.init(type: .custom), init(type: .custom)
內(nèi)部怎么實現(xiàn)的儒搭,和你沒有關(guān)系。
不具備業(yè)務場景的功能組件
這類在項目中非常的常見芙贫。但是搂鲫,但是,也是非常重要的磺平∧ǎ可以說是在項目中最底層的、很少變動的褪秀。如:Database組件蓄诽、網(wǎng)絡(luò)組件、國際化等媒吗。從整個項目來說仑氛,是位于組件化分組的最底層,涉及范圍廣闸英,如果改動锯岖,付出的代價太大。對待這類組件甫何,我們的原則是指增加接口出吹,盡量、盡量少的改動辙喂,除非不影響大家使用捶牢。
組件之間的通信
Mediator進行通信
Protocol協(xié)議方案
Mediator進行通信
針對這種方案大佬bang提出了一些問題,并且與Mediator進行通信進行了對比
需要有個地方列出各個組件里有什么 URL 接口可供調(diào)用巍耗。蘑菇街做了個后臺專門管理
每個組件都需要初始化秋麸,內(nèi)存里需要保存一份表,組件多了會有內(nèi)存問題
參數(shù)的格式不明確炬太,是個靈活的 dictionary灸蟆,也需要有個地方可以查參數(shù)格式
在愚公編程MrPeak:iOS組件化方案中 中對3個問題進行了詳細的解釋
第一個問題是最明顯的問題,組件的使用方必須通過查閱web文檔之后亲族,再手寫string來完成調(diào)用炒考。這種組件調(diào)用方式確實會有一定的效率問題可缚。
第二個問題所說的表和內(nèi)存問題我沒理解具體是指哪一塊。我算了下Router當中的額外內(nèi)存開銷斋枢,一個用來存儲Mapping的NSMutableDictionary帘靡,iOS App當中使用Dictionary的場景會很多,Dictionary帶來的內(nèi)存開銷主要看其所強引用的key和value杏慰。二是以URLPattern為Key的各種string测柠,這個估計是大頭炼鞠,但Casa的方案里將Action以String的方式hardcode缘滥,也會導致這些String常住內(nèi)存,其本質(zhì)是將原本處于Text區(qū)的函數(shù)符號換成了位于Data區(qū)的string谒主,此消彼長朝扼,這部分內(nèi)存消耗也在正常范圍之內(nèi),最后是handler block霎肯,這部分開銷也屬于常規(guī)使用擎颖,和一次函數(shù)調(diào)用并沒有本質(zhì)區(qū)別,看上去內(nèi)存消耗總量并沒有特別增長观游,或許還有其他我沒考慮到的部分搂捧。
第三個問題其實和第一個問題是類似的,需要查閱文檔來hardcode參數(shù)名稱懂缕。
我們目前用的是這種方案允跑,其他的還沒有嘗試過。組件A register,組件B push
Router.register(xxxxx) { (url, values, context) -> UIViewController
Router.register(xxxxx) { (url, vaules, context) -> UIViewController
Protocol協(xié)議方案
我們也考慮過這種搪柑,在拜讀 Limboy // bang // 愚公編程MrPeak文章之后聋丝,我們選擇了中間的方案。在以上大佬的博客中都闡明了各種組件跳轉(zhuǎn)的有點和缺點工碾。
關(guān)于私有庫創(chuàng)建
這種問題百度或者google一下就一大片弱睦,創(chuàng)建私有庫