掌上鏈家iOS組件化
組件化目標(biāo)
我們組件化的目標(biāo)就是每一個(gè)組件都是一個(gè)私有的倉(cāng)庫(kù)啼肩,都是一個(gè)pod,殼工程用的時(shí)候直接pod install
每個(gè)組件可以看做一個(gè)小的app,里面使用MVC架構(gòu)
不參考原來(lái)的邏輯癣猾,在寫(xiě)這個(gè)模塊的時(shí)候和產(chǎn)品確認(rèn)邏輯,把重要的模塊全部重寫(xiě)按灶,你在抽離這個(gè)模塊的時(shí)候鼓拧,要跟其他模塊完全解耦嗜傅,在你的模塊中不能調(diào)用其他模塊的任何代碼瘦锹,你要保證你的模塊在你的測(cè)試App(你的測(cè)試殼工程)里能夠完全運(yùn)行起來(lái),就要達(dá)到這個(gè)目標(biāo)
準(zhǔn)備工作:
1方库、制定代碼規(guī)范结序,類(lèi)前綴,文件前綴纵潦,縮進(jìn)徐鹤,注釋規(guī)范
2、建議在開(kāi)始的時(shí)候大家都在一個(gè)工程里面就行開(kāi)發(fā)酪穿,因?yàn)樵陂_(kāi)發(fā)重構(gòu)的過(guò)程中凳干,代碼在不斷的變化相當(dāng)于每一個(gè)私有倉(cāng)庫(kù)版本號(hào)在不斷的進(jìn)行更新,每新增一些代碼都必須重新打個(gè)包工作量比較大
3被济、通過(guò)分文件夾的方式每個(gè)組件都有一個(gè)對(duì)應(yīng)的文件夾, 對(duì)應(yīng)組件的源碼都在對(duì)應(yīng)組件文件夾下面開(kāi)發(fā)涧团。需要保證的是你在組件里面的代碼只磷,不會(huì)引用到其他組件的代碼,不會(huì)import其他組件的文件泌绣,也不會(huì)使用其他組件的方法
4钮追、開(kāi)發(fā)完以后再給對(duì)應(yīng)的組件編寫(xiě)podspec文件,再把它弄到倉(cāng)庫(kù)里面阿迈,再給它更新版本號(hào)元媚,再把最終的版本號(hào)填寫(xiě)到殼工程里面去,創(chuàng)建整個(gè)殼工程
抽離公共基礎(chǔ)組件:
跨產(chǎn)品使用的東西苗沧,和業(yè)務(wù)沒(méi)關(guān)系和產(chǎn)品也是沒(méi)關(guān)系的刊棕,log日志,網(wǎng)絡(luò)待逞,地圖服務(wù)甥角,社會(huì)化分享,文件服務(wù)识樱,category擴(kuò)展
產(chǎn)品相關(guān)的基礎(chǔ)庫(kù):
和當(dāng)前公司產(chǎn)品強(qiáng)相關(guān)的嗤无,包括通用的base(BaseViewController等)震束,自定義的HUD、圖片選擇器当犯、用戶(hù)行為統(tǒng)計(jì)庫(kù)垢村、應(yīng)用基本配置信息(token、簽名嚎卫、cookie肝断、加解密相關(guān))以及其他自定義的UI基礎(chǔ)組件 (和產(chǎn)品強(qiáng)相關(guān)和具體業(yè)務(wù)不相關(guān))
把以上公共基礎(chǔ)組件、鏈家產(chǎn)品基礎(chǔ)庫(kù)進(jìn)行剝離驰凛,所謂的剝離就是把以上的代碼變成一個(gè)個(gè)的私有庫(kù)胸懈,以源碼的形式打成一個(gè)pod,使用的時(shí)候pod install 怎么使用Cocoa Pod 發(fā)布自己的SDK恰响?
組件內(nèi)使用MVC架構(gòu)的調(diào)用規(guī)則
對(duì)于比較復(fù)雜的邏輯:
1趣钱、可以加一個(gè)logic層,把一些復(fù)雜的業(yè)務(wù)邏輯放到里面胚宦,所謂的復(fù)雜業(yè)務(wù)邏輯 就可能會(huì)在你的業(yè)務(wù)里面保存一些狀態(tài)機(jī)或者處理復(fù)雜的業(yè)務(wù)情況
2首有、比如登錄模塊,可能要處理一些狀態(tài)機(jī)枢劝,還要處理各種異常的情況井联,還有注冊(cè),可能還有第三方登錄您旁,相對(duì)來(lái)說(shuō)登錄還是比較復(fù)雜的烙常,所以我在里面又加了一層Logic層,專(zhuān)門(mén)處理一些復(fù)雜的業(yè)務(wù)邏輯鹤盒,對(duì)于復(fù)雜的組件還是很有必要的蚕脏,它可以把那些ViewController的代碼給解放出來(lái)
1、UI層只能調(diào)用Resource侦锯、Logic驼鞭、Model的代碼,不能在Logic層反調(diào)UI層的代碼尺碰,如果Logic層想要調(diào)用UI層代碼挣棕,只能通過(guò)通知/Block的方式回調(diào)
2、Service層只做service相關(guān)的動(dòng)作亲桥,它只能夠請(qǐng)求網(wǎng)絡(luò)洛心,然后把請(qǐng)求回來(lái)的數(shù)據(jù)返回,不能去調(diào)用Logic層的任何東西两曼,更不能去調(diào)UI層的東西
3皂甘、Model層就完全是Model,完全解耦,沒(méi)任何依賴(lài)悼凑,Model里面不能有任何的業(yè)務(wù)代碼偿枕,不能有任何的業(yè)務(wù)邏輯
Service層:
處理該組件網(wǎng)絡(luò)接口的請(qǐng)求璧瞬、以及服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)model的工作
UI層:
頁(yè)面相關(guān)的事務(wù)處理就放在對(duì)應(yīng)的ViewController里面,界面相關(guān)的全部放在storyboard里面去做
組件管理中心(LJComponentManager)
組件之間的交互無(wú)外乎就兩個(gè)東西:下面這兩個(gè)問(wèn)題解決了渐夸,組件與組件之間的通訊問(wèn)題就解決了
1嗤锉、組件之間頁(yè)面之間的跳轉(zhuǎn)
2、組件之間服務(wù)的調(diào)用
組件之間跳轉(zhuǎn):
可以采用openUrl的方式墓塌,就是schemes的方式瘟忱,因?yàn)樘O(píng)果本身是支持scheme的,只要傳一個(gè)scheme一個(gè)類(lèi)似于HTTP后面是名字然后苫幢?后面跟參數(shù)
都在組件管理中心存在的問(wèn)題访诱?
如果所有的跳轉(zhuǎn)的都在組件管理中心的router函數(shù)來(lái)跳轉(zhuǎn)的話(huà),如果有30個(gè)組件,每個(gè)組件跳轉(zhuǎn)10個(gè)界面韩肝,router函數(shù)就300多行了触菜,函數(shù)超過(guò)50行,100行就不是一個(gè)好的編程方式
解決方案:URL導(dǎo)航去中心化
1、去中心化哀峻,頁(yè)面的跳轉(zhuǎn)交給每個(gè)組件自己去做涡相,業(yè)務(wù)組件注冊(cè)自己能夠處理的URL,并且返回對(duì)應(yīng)的VC
2、如果某個(gè)URL你這個(gè)組件無(wú)法處理剩蟀,或者是參數(shù)不對(duì)催蝗,就直接返回nil
3、ComponentManager這邊什么都不做育特,ComponentManager做的事情就是一個(gè)中間件丙号,就是做一個(gè)轉(zhuǎn)發(fā),ComponentManager接到一個(gè)URL,它把這個(gè)URL傳遞給對(duì)應(yīng)的組件且预,讓這個(gè)組件自己去查詢(xún)這個(gè)URL對(duì)應(yīng)的VC
4槽袄、這個(gè)組件查詢(xún)到VC過(guò)后,把VC返回給ComponentManager锋谐,ComponentManager拿到VC過(guò)后,它再去調(diào)用LJNavigator去進(jìn)行路由跳轉(zhuǎn)截酷,至于是push也好涮拗,present也好就根據(jù)具體的參數(shù)去控制,跳轉(zhuǎn)根據(jù)參數(shù)解析
組件之間服務(wù)調(diào)用:
服務(wù)之間的調(diào)用也很簡(jiǎn)單迂苛,比如說(shuō)我在某個(gè)組件想要調(diào)用首頁(yè)組件的某一個(gè)服務(wù)三热,那我只要傳遞首頁(yè)組件協(xié)議的名稱(chēng),通過(guò)傳入?yún)f(xié)議名的方式去調(diào)用它某一個(gè)對(duì)外暴露的接口就可以了
1三幻、每個(gè)組件提供對(duì)外的接口文件就漾,把所有需要對(duì)外暴露的接口,都寫(xiě)在接口文件里面
2念搬、組件實(shí)現(xiàn)和組件管理中心之間增加了一層協(xié)議接口層(LJServiceProtocol集合)
接口文件的設(shè)計(jì)原則:
1抑堡、最復(fù)雜的二手房組件對(duì)外提供的接口也就5個(gè)左右摆出,也就查詢(xún)二手房,搜索首妖,跳轉(zhuǎn)偎漫,一個(gè)組件對(duì)外提供的接口我認(rèn)為是不會(huì)超過(guò)10個(gè)的,如果一個(gè)組件對(duì)外提供的服務(wù)接口超過(guò)10個(gè)有缆,那我就建議你把你的這個(gè)組件拆分成兩個(gè)組件象踊,或者更多組件
2、不建議使用動(dòng)態(tài)的方法(runtime)實(shí)現(xiàn),用這種對(duì)外提供接口棚壁,已經(jīng)能夠滿(mǎn)足要求了
方案對(duì)比:
1杯矩、這種方案比較直觀(guān),比較容易接受袖外,你只要把對(duì)外服務(wù)集合的接口拉下來(lái)史隆,就可以看到所有組件對(duì)外暴露哪些接口
2、動(dòng)態(tài)化的方式可能就不那么直觀(guān)在刺,直接把string變成類(lèi)名逆害,要看一下這個(gè)string對(duì)應(yīng)哪個(gè)類(lèi),這個(gè)類(lèi)提供了哪些方法蚣驼,動(dòng)態(tài)性高會(huì)失去一部分可讀性
對(duì)外服務(wù)協(xié)議集合
二手房組件有個(gè)SecondHandHouseProtocol,地圖組件有個(gè)LJMapProtocol,搜索組件有個(gè)SearchProtocol,所有的接口文件魄幕,我把它組成一個(gè)集合叫做業(yè)務(wù)組件對(duì)外服務(wù)協(xié)議集合,所有對(duì)外服務(wù)的接口必須要有一個(gè)獨(dú)立的版本倉(cāng)庫(kù)去管理
為什么要用單獨(dú)的git倉(cāng)庫(kù)地址去管理颖杏?
1纯陨、一旦你對(duì)外提供的接口文件確定了,那么你這個(gè)組件就不能對(duì)這個(gè)接口進(jìn)行增刪改了
2留储、如果你要新增一個(gè)接口或者修改接口名字修改一個(gè)參數(shù)翼抠,你這個(gè)組件版本更新了一定要更新對(duì)應(yīng)的協(xié)議接口文件的版本號(hào),一旦你這個(gè)服務(wù)協(xié)議集合的某一個(gè)接口版本變化了获讳,一定要把整個(gè)協(xié)議集合的版本進(jìn)行更新
3阴颖、把整個(gè)對(duì)外服務(wù)協(xié)議集合把它抽出來(lái),做一個(gè)大的git地址丐膝,用一個(gè)git倉(cāng)庫(kù)進(jìn)行管理量愧,一旦你變化組件對(duì)外提供的服務(wù)變化,就要進(jìn)行版本變化帅矗,一旦版本變化就要通知所有引用協(xié)議服務(wù)集合的依賴(lài)方偎肃,讓他去更新你的版本號(hào),讓他去pod update
接入點(diǎn):
1浑此、中國(guó)要加入世貿(mào)組織累颂,怎么加入呢,你肯定是要遵守這個(gè)組織的一些協(xié)議一些規(guī)章制度凛俱,組件也一樣紊馏,你這個(gè)組件想要接入組件管理中心料饥,想要接入這套組件管理框架,你就要遵守這套組件管理的接入?yún)f(xié)議瘦棋,這個(gè)協(xié)議就叫做接入點(diǎn)協(xié)議LJConnectorProtocol稀火,你這個(gè)組件只要遵循了我這個(gè)協(xié)議,那么你就可以接入我這個(gè)管理中心赌朋,你就可以被我這個(gè)中心管理器發(fā)現(xiàn)凰狞,進(jìn)行調(diào)度
2、同時(shí)這個(gè)接入點(diǎn)也可以遵循一個(gè)LJComponentServiceProtocol協(xié)議沛慢,這個(gè)協(xié)議就是一個(gè)對(duì)外服務(wù)的協(xié)議赡若,所謂的對(duì)外提供的服務(wù)就是對(duì)外提供的接口都寫(xiě)在了這個(gè)協(xié)議里,其他組件都可以通過(guò)組件管理器進(jìn)行發(fā)現(xiàn)調(diào)度团甲,就是組件之間可以進(jìn)行服務(wù)的調(diào)度逾冬,任何組件只要你遵循這兩個(gè)協(xié)議,你就可以接入我這個(gè)組件化的架構(gòu)
任何組件只要遵守了這些協(xié)議躺苦,就可以接入組件管理中心身腻,組件管理中心在進(jìn)行任務(wù)調(diào)度的時(shí)候就可以發(fā)現(xiàn)這個(gè)組件,進(jìn)行頁(yè)面跳轉(zhuǎn)服務(wù)的調(diào)用
1匹厘、ComponentManager判斷你這個(gè)組件能否handle這個(gè)URL
2嘀趟、這個(gè)URL對(duì)應(yīng)的ViewController
3、業(yè)務(wù)組件注冊(cè)自己能提供的Service愈诚,返回服務(wù)實(shí)例
解耦妥協(xié):公共Model下沉
Model與字典傳參的優(yōu)缺點(diǎn):
1她按、使用字典優(yōu)點(diǎn)是完全解耦,如果有幾十個(gè)參數(shù)的話(huà)炕柔,要一個(gè)一個(gè)拼接字典容易出錯(cuò)酌泰,可讀性差,接收方又要轉(zhuǎn)model進(jìn)行解析
2匕累、使用Model傳遞的話(huà)陵刹,會(huì)有一定的耦合性,非硬編碼不會(huì)出現(xiàn)拼寫(xiě)錯(cuò)誤
組件之間傳遞復(fù)雜參數(shù): 開(kāi)發(fā)效率欢嘿,可讀性授霸,耦合上做一個(gè)平衡
1、幾百個(gè)參數(shù)傳遞起來(lái)非常的麻煩际插,我們就想看看在組件里面有哪些Model是被公共使用的,我們有三十多個(gè)組件显设,用到的公共Model也就十幾個(gè)框弛,能夠想出來(lái)的也就房子的Model,經(jīng)紀(jì)人的model,搜索的Model,用戶(hù)的Model捕捂,其實(shí)也就幾個(gè)Model 經(jīng)常用的瑟枫,為什么不把這些Model下沉到底部斗搞,把它專(zhuān)門(mén)做成一個(gè)podspec,把它下沉到底層庫(kù)去,所有的模塊都可以依賴(lài)這個(gè)公共Model
2慷妙、好處就是接口的暴露可以使用Model進(jìn)行傳遞而不是使用字典傳遞僻焚,可能會(huì)打破一些封裝性,打破一些組件之間的依賴(lài)膝擂,但是便利性和效率上非陈瞧。快,下沉的過(guò)程中你會(huì)發(fā)現(xiàn)也不會(huì)太多架馋,也就十幾個(gè) 公共Model,設(shè)計(jì)好之后只會(huì)往里面加一些屬性狞山,并不會(huì)刪改一些屬性,變化頻率不會(huì)太高叉寂,但一定會(huì)變化的萍启,所以必須把公共Model庫(kù)單獨(dú)拿出來(lái),使用podspec管理屏鳍,一旦公共model的屬性增刪改了以后勘纯,一定要更新它的版本號(hào),周知業(yè)務(wù)方
掌上鏈家組件化架構(gòu)
1钓瞭、上面的每一個(gè)小豆腐塊都是單獨(dú)的私有倉(cāng)庫(kù)驳遵,單獨(dú)的podfile,單獨(dú)可以去打包降淮,打包成framework超埋,或者打包成源碼都可以
2、整個(gè)鏈家其實(shí)是兩個(gè)app,一個(gè)是賣(mài)家APP佳鳖,一個(gè)是買(mǎi)家APP霍殴, 有兩個(gè)入口,所有的源碼都是在podfile里面系吩,最后理想的情況寫(xiě)一個(gè)殼来庭,把需要的功能podfile進(jìn)去,就是一個(gè)新的APP
管理器倉(cāng)庫(kù)
1穿挨、有一些管理器一些組件要用另一些組件也要用月弛,用的多了給外部暴露太多的接口不好,那還不如把它做成一個(gè)單例科盛,把這些作為單例的manager做成一個(gè)管理器庫(kù)帽衙,把它下沉到下面去,這樣所有上層的一些業(yè)務(wù)都可以調(diào)用它
2贞绵、而且管理器內(nèi)部又可以管理他自己的一些狀態(tài)厉萝,一些變量,相對(duì)而言也比較獨(dú)立,擴(kuò)展也比較好
3谴垫、 定位相關(guān)的章母,數(shù)據(jù)緩存、持久化相關(guān)的翩剪,版本管理乳怎、push管理、城市管理前弯、 廣告的管理蚪缀、聯(lián)系人的管理,單例上層調(diào)用起來(lái)非常方便
4博杖、在登錄組件中會(huì)有一個(gè)LoginManager椿胯,我們把它下沉到底部去了,LoginManager管理登錄相關(guān)的底層信息剃根,比如登錄的狀態(tài)哩盲,當(dāng)前在登錄狀態(tài),還是非登錄狀態(tài)狈醉,還是注冊(cè)狀態(tài)廉油,這個(gè)狀態(tài)機(jī)我們是下沉管理的,我們把一個(gè)LoginManager放到下面去苗傅,其他組件如果需要用到登錄相關(guān)的狀態(tài)抒线,比如點(diǎn)贊,發(fā)評(píng)論渣慕,個(gè)人設(shè)置需要知道登錄的狀態(tài)機(jī)嘶炭,只要調(diào)用LoginManager單例的狀態(tài)就可以了,LoginManager如果需要監(jiān)聽(tīng)登錄成功的事件逊桦,只要注冊(cè)manager里面登錄的一些回調(diào)眨猎, 或者登錄的一些通知,就可以在組件里面監(jiān)聽(tīng)登錄的事件
URL動(dòng)態(tài)部署和容錯(cuò)處理
使用場(chǎng)景:
由于一些政策原因强经,不能顯示之前跳轉(zhuǎn)的頁(yè)面睡陪,要重定向一些頁(yè)面的跳轉(zhuǎn)
使用方法:
程序啟動(dòng)時(shí)從服務(wù)器下載Json格式的Scheme集合,匿情?之前是原來(lái)跳轉(zhuǎn)的界面兰迫,?之后是新跳轉(zhuǎn)的界面炬称,解析Url時(shí)進(jìn)行處理
遇到的一些問(wèn)題
切換環(huán)境的頁(yè)面怎么做汁果?
我們當(dāng)時(shí)在首頁(yè)用三根手指點(diǎn)擊四次會(huì)彈出一個(gè)頁(yè)面,頁(yè)面上可以選擇測(cè)試環(huán)境玲躯,生產(chǎn)環(huán)境须鼎,然后切換之后會(huì)提示你鲸伴,環(huán)境已切換需要重新啟動(dòng),就是你設(shè)置之后在文件里面寫(xiě)一個(gè)值晋控,然后根據(jù)這個(gè)值去判斷是生成環(huán)境還是測(cè)試環(huán)境,啟動(dòng)時(shí)會(huì)優(yōu)先讀值姓赤,然后網(wǎng)絡(luò)環(huán)境跟著這個(gè)值走赡译,發(fā)布的時(shí)候要把這個(gè)入口給關(guān)掉,避免用戶(hù)切換到測(cè)試環(huán)境
組件的拆分粒度就是你組件化的抽象能力
比如最初設(shè)計(jì)我們二手房不铆,新房蝌焚,學(xué)區(qū)房模塊的時(shí)候,最初是把房子和搜索做在一起的誓斥,我們發(fā)現(xiàn)搜索在二手房也有只洒,新房里面也有,學(xué)區(qū)房里面也有劳坑,而且搜索的內(nèi)容差不多毕谴,都是傳入一定的參數(shù),返回的都是房子列表距芬,房子列表的數(shù)據(jù)結(jié)構(gòu)其實(shí)都是差不多 涝开,后來(lái)我們就想能不能把搜索這邊再抽出來(lái)做一個(gè)搜索的組件,如果你在房子的組件里面要用到搜索相關(guān)的功能框仔,去調(diào)用它的接口去傳相應(yīng)的參數(shù)給它就可以了
組件的scheme是如何管理的舀武?
每一個(gè)組件管理自己的scheme,管理自己的URL离斩, 它里面有一個(gè)函數(shù)叫做openUrl,在這個(gè)openUrl函數(shù)里面其實(shí)就是一個(gè)if else的結(jié)構(gòu)银舱,每個(gè)組件可能有五六個(gè)甚至十幾個(gè)if else ,所有它能夠handle的scheme都在這個(gè)組件內(nèi)部進(jìn)行管理的
業(yè)務(wù)code是放在網(wǎng)絡(luò)層處理,還是在業(yè)務(wù)層處理跛梗?
1寻馏、業(yè)務(wù)的code肯定是放在業(yè)務(wù)的code里面處理的,網(wǎng)絡(luò)層不會(huì)處理任何的業(yè)務(wù)茄袖,組件內(nèi)部不是有一個(gè)Service嗎操软?Service層只做發(fā)送請(qǐng)求,服務(wù)端返回json文件宪祥,把json簡(jiǎn)單的拋給Logic層處理去做json轉(zhuǎn)model的操作聂薪,Service層不會(huì)把某一個(gè)字段解析出來(lái),看是什么type要不要做一些特殊的處理蝗羊,網(wǎng)絡(luò)層只做網(wǎng)絡(luò)層應(yīng)該做的事藏澳,只做收發(fā)信令,只做回調(diào)這個(gè)事情耀找,至于業(yè)務(wù)是上層的logic層在做的
不同組件中翔悠,資源重復(fù)怎么處理业崖?
如果把資源文件下沉,由于很多組件的資源在不停的變化蓄愁,一同去維護(hù)資源的版本號(hào)非常的麻煩双炕,最后我們決定把每一個(gè)資源拆分到自己的組件里面去,雖然會(huì)造成各個(gè)組件里面一些資源的重復(fù)撮抓,但是這些資源浪費(fèi)是在合理的范圍之內(nèi)妇斤,也沒(méi)有太大,像一些啟動(dòng)圖片丹拯,引導(dǎo)圖片完全可以放到殼工程里面去站超,建議每個(gè)組件里面要有自己的資源
同一個(gè)工程多個(gè)組件如何協(xié)同開(kāi)發(fā)?
組件化設(shè)計(jì)建議:
1乖酬、組件之間如果用到相同的cell死相,建議多份拷貝,不要下沉咬像,在新房算撮、二手房的房源列表里面發(fā)現(xiàn)其實(shí)很多cell都長(zhǎng)得差不多 ,開(kāi)始我們想要復(fù)用就把cell抽到底層的公共控件庫(kù)去了施掏,后來(lái)我們發(fā)現(xiàn)沒(méi)有這個(gè)必要钮惠,哪怕兩個(gè)組件的cell一模一樣,你單獨(dú)拷一份重新命名七芭,跟著組件走素挽,千萬(wàn)不要把它下沉到底部去,一旦你開(kāi)了這個(gè)先例狸驳,你會(huì)發(fā)現(xiàn)以后往下沉的東西越來(lái)越多预明,慢慢的就失去了組件的意義了,所有東西都往下沉就變成原來(lái)的MVC架構(gòu)了
2耙箍、非常不建議在項(xiàng)目中有common控件和common字樣撰糠,任何組件一定是分工明確功能單一的,如果一開(kāi)始有common辩昆,慢慢的越來(lái)越多的垃圾往里面放阅酪,到最后就沒(méi)法維護(hù)了千萬(wàn)不要有common組件