深入谈一谈iOS模块独立运行
背景
最近一直在團(tuán)隊(duì)推進(jìn)關(guān)于iOS模塊獨(dú)立運(yùn)行相關(guān)的事項(xiàng),想把最近的一些想法和實(shí)施情況通過這篇文章做一個(gè)記錄。
如果在一個(gè)項(xiàng)目中,某一塊代碼足夠獨(dú)立(功能、業(yè)務(wù)上),就會(huì)傾向于將他通過Cocoapods抽離為一個(gè)pods文件。通過一個(gè)podspec文件描述這個(gè)pod的信息。
最直接的方式就是把相關(guān)文件組織好遷移到一個(gè)目錄下,通過podspec對(duì)源碼位置,資源位置等信息的描述,完成一個(gè)最簡(jiǎn)單直白的pod創(chuàng)建。
并且在Podfile中通通過
pod 'XXX', :path => '~/dev/XXX' 復(fù)制代碼這種方式,集成到主工程進(jìn)行開發(fā)。開發(fā)自測(cè)完成后通過私有repo發(fā)布,并且將Podfile中的指向版本
pod 'XXX', '1.0.0' 復(fù)制代碼然后我們就會(huì)說,抽離了一個(gè)庫,項(xiàng)目的代碼結(jié)構(gòu)變得更合理更清晰了。
但這么做,在我看來跟在項(xiàng)目里直接用group把這些代碼區(qū)別開來沒什么區(qū)別,反而要修改的時(shí)候還要重新git pull、pod install變得更加麻煩了。
因?yàn)檫@么做被分離的代碼無法獨(dú)立運(yùn)行,而且由于依賴不清晰,沒辦法共享給其他項(xiàng)目使用,導(dǎo)致這種分離方式是一種偽解耦,該有的益處沒有展現(xiàn)出來,修改的時(shí)候反倒更加麻煩,這絕對(duì)不是我們想要的效果。
由此引出對(duì)模塊獨(dú)立運(yùn)行
對(duì)iOS模塊獨(dú)立運(yùn)行的思考
個(gè)人心目中完美的Pod應(yīng)該在這四個(gè)維度上做到最好。
-
依賴足夠清晰 清楚的描述自己的依賴狀況。說到這個(gè)不得不吐槽一個(gè)iOS項(xiàng)目與Cocoapods結(jié)合之后一個(gè)奇怪的現(xiàn)象,就是Pod如果在主工程中通過path引入的,那么,在不聲明清楚自身以來的情況下,可以使用主工程內(nèi)所有類,甚至是主工程的類,并且不會(huì)得到任何提示,這種情況很普遍,并且事后想要理清依賴的成本極高,這個(gè)Pod算是廢了(沒有了抽離Pod的意義了)。
-
方便共享給其他項(xiàng)目使用 我的理解,抽離Pod的很大一個(gè)目的不就是為了共享嗎?如果不是為了這個(gè)目的,其實(shí)沒必要抽離Pod的,反而更加麻煩了,只跟一個(gè)項(xiàng)目綁死的Pod在我看來,完全沒有必要做成Pod,項(xiàng)目里放在Group就可以了。而為了能達(dá)到這個(gè)目的需要克服很多的困難。
-
方便快速修改驗(yàn)證 隨著主工程越來越大,編譯速度越來越慢,開發(fā)效率也在無形之中慢慢降低。Pod從主工程中脫離出來獨(dú)立運(yùn)行,單獨(dú)編譯,隔絕對(duì)主工程的依賴,完全自給自足,這樣代碼編譯量就會(huì)大大降低,達(dá)到開發(fā)效能提高的目的。
-
自身質(zhì)量保障 在第三點(diǎn)的基礎(chǔ)上,帶上完全針對(duì)這個(gè)Pod的單元測(cè)試UI測(cè)試,完美顆粒化代碼的同時(shí),還能很好的保障自身的質(zhì)量,并且清晰易維護(hù),這塊如果配合Xcode Server,會(huì)發(fā)揮強(qiáng)大的優(yōu)勢(shì)。
然而要做到這些維度上的最好,需要克服很多問題,接下來慢慢道來。
兩種類型的代碼
類似AFNetworking、SDWebImage這樣的功能型代碼,分離的一個(gè)只需要確保自己的依賴清晰,被依賴的時(shí)候使用方便就可以了。而對(duì)于偏業(yè)務(wù)型的代碼就不那么容易了,通常會(huì)有界面,還會(huì)有各種業(yè)務(wù)帶來的附加產(chǎn)物,例如打點(diǎn),例如網(wǎng)絡(luò)庫各種規(guī)則。
因?yàn)楣δ苄痛a本身對(duì)于以上四點(diǎn)門檻不高,我就不展開討論了,主要還是展開說一下業(yè)務(wù)型代碼。
業(yè)務(wù)型代碼獨(dú)立運(yùn)行的看法
推進(jìn)業(yè)務(wù)型代碼獨(dú)立運(yùn)行過程中遇到的一些問題列舉:
業(yè)務(wù)代碼需要用到 打點(diǎn)、網(wǎng)絡(luò)等基本能力,背后每個(gè)能力都可能牽扯出一堆間接依賴,但這些依賴跟這個(gè)Pod本身沒任何屁關(guān)系,同時(shí)還會(huì)是不是的出現(xiàn)間接依賴不明確導(dǎo)致的編譯報(bào)錯(cuò)問題。
作為已經(jīng)是獨(dú)立可運(yùn)行的Pod了,界面什么的都自己hold了,那么它一定還需要跟其他本身之外的幾面進(jìn)行交互,舉個(gè)例子,一個(gè)Product的Pod,需要跳轉(zhuǎn)到Order中的一個(gè)界面,或者Chat中的一個(gè)界面,而這個(gè)界面在代碼層面根本不存在,要如何處置。
之前也提到了,一個(gè)依賴清晰的獨(dú)立運(yùn)行Pod如果被不小心path方式開發(fā)了一次,那么這個(gè)Pod會(huì)慢慢變廢,下次運(yùn)行可能就不能運(yùn)行了,所以還要想辦法要怎么不被path依賴。
還有一個(gè)比較頭疼的問題是,隨著業(yè)務(wù)迭代,某個(gè)冷門的獨(dú)立運(yùn)行Pod并沒有跟上腳步,其直接依賴的功能庫在主工程都更新了,但它卻全然不知,難道還要一個(gè)一個(gè)校對(duì)嗎?
解決了基礎(chǔ)能力的間接依賴,各種必要的直接依賴的間接依賴也會(huì)出現(xiàn)不明確而出現(xiàn)的編譯失敗問題,需要解決,確保只有代碼api需要更新時(shí)才會(huì)編譯不過,才是最爽的開發(fā)流程。
思考實(shí)施解決之道
實(shí)施過程中開發(fā)了兩套工具來解決。均未開源,外網(wǎng)勿搜。下面介紹詳細(xì)思路。
腳手架工具 - gearmaker
避免被path模式開發(fā)
好不容建了一個(gè)獨(dú)立運(yùn)行Pod,要是不小心被不明真相的同學(xué)用path開發(fā)了就糟了。如何避免被path模式開發(fā)呢?如果是源碼模式下,其實(shí)是做不到的。所以我們把每個(gè)獨(dú)立運(yùn)行的Pod的產(chǎn)物定位二進(jìn)制庫,靜態(tài)動(dòng)態(tài)都可,podspec層面就不允許指向源碼,想要修改源碼,只能通過獨(dú)立運(yùn)行的工程進(jìn)行修改。
具體操作這里不展開了,如果podspec指向的是靜態(tài)庫,而沒有源碼指向則這個(gè)Pod理論上不可能不可被path模式開發(fā)。
而如果通過Cocoapods 官方建議的 pod lib create 方式創(chuàng)建,則Pod代碼會(huì)存在于 Development Pods 下,而必須在podspec中指定源碼路徑,因此我修改了 pod lib create 的腳手架模板,將Pod代碼直接放入項(xiàng)目的一個(gè)Group中,而Group對(duì)應(yīng)產(chǎn)物是一個(gè)framework,podspec直接指向podspec,外加Universal打包腳本就可以啦。
有同學(xué)有疑問了,那源碼調(diào)試怎么辦呢?這個(gè)不用擔(dān)心,既然Pod已經(jīng)可以獨(dú)立運(yùn)行,有什么問題需要調(diào)試,是都可以在Pod工程中進(jìn)行數(shù)據(jù)Mock來還原問題的,所以主工程只是用來集成,不需要考慮調(diào)試問題。
即便是特別特殊的情況,只在主工程能還原,那臨時(shí)加一下podspec指向本地path做一下debug也是可以的。
版本仲裁 & 保鮮
Pod獨(dú)立運(yùn)行工程的一大詬病就是時(shí)間一長(zhǎng),工程就無法編譯通過運(yùn)行了,并且哪些依賴需要更新,需要詳細(xì)對(duì)比,成本非常高,很多遇到這樣情況就會(huì)放棄Pod工程。
為此gearmaker中集成了版本仲裁能力,通過hook pod命令,在pod install之前,計(jì)算出指定客戶端主工程最新的依賴全集,在pod install時(shí),在這個(gè)全集中找到仲裁版本來使用。
這樣一來,pod install后的Pod工程所有依賴必然與主工程一致,只需要修改因?yàn)橐蕾嚫聨淼南嚓P(guān)api變更即可通過編譯正常運(yùn)行,確保Pod工程不會(huì)腐敗。
依賴切斷 服務(wù)提供組件 - ServiceProvider
所有Pod只需要直接依賴ServiceProvider,由ServiceProvider來統(tǒng)一提供服務(wù)能力,包括Pod工程本身需要的任何能力。比如,路由、打點(diǎn)、網(wǎng)絡(luò)、從另一個(gè)模塊獲取數(shù)據(jù),獲取一個(gè)View對(duì)象等,均不需要依賴其他庫,直接從ServiceProvider中通過內(nèi)置的protocol來獲得,并使用。
提供能力的一方對(duì)預(yù)先放置在ServiceProvider中的protocol進(jìn)行功能實(shí)現(xiàn),通過以下方式將自身的能力注冊(cè)入ServiceProvider,即可為其他提供能力,而不需要依賴。
注冊(cè)服務(wù)
[ServiceProvider registService:[XMUserTrack class] withProtocol:@protocol(UserTrack)]; 復(fù)制代碼獲取服務(wù)
id<UserTrack> ut = [ServiceProvider serviceWithProtocol:@protocol(UserTrack)]; 復(fù)制代碼使用體驗(yàn)
對(duì)于新的Pod創(chuàng)建,直接使用以下命令創(chuàng)建:
gearmaker <PodName> 復(fù)制代碼根據(jù)命令行提示進(jìn)行創(chuàng)建即可。創(chuàng)建完成的腳手架直接提供了ServiceProvider,開發(fā)同學(xué)直接從ServiceProvider中獲取服務(wù)進(jìn)行Pod開發(fā),開發(fā)完成后通過Universal腳本生成framework上傳到私有repo中定版本即可直接使用。
總結(jié)
以上是生活随笔為你收集整理的深入谈一谈iOS模块独立运行的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入浅出C/C++中的正则表达式库(二)
- 下一篇: 干货!9种高性能可用高并发的技术架构