深入談一談iOS模組獨立執行

劉哈發表於2017-12-17

背景

最近一直在團隊推進關於iOS模組獨立執行相關的事項,想把最近的一些想法和實施情況通過這篇文章做一個記錄。

如果在一個專案中,某一塊程式碼足夠獨立(功能、業務上),就會傾向於將他通過Cocoapods抽離為一個pods檔案。通過一個podspec檔案描述這個pod的資訊。

最直接的方式就是把相關檔案組織好遷移到一個目錄下,通過podspec對原始碼位置,資源位置等資訊的描述,完成一個最簡單直白的pod建立。

並且在Podfile中通通過

pod 'XXX', :path => '~/dev/XXX'
複製程式碼

這種方式,整合到主工程進行開發。開發自測完成後通過私有repo釋出,並且將Podfile中的指向版本

pod 'XXX', '1.0.0'
複製程式碼

然後我們就會說,抽離了一個庫,專案的程式碼結構變得更合理更清晰了。

但這麼做,在我看來跟在專案裡直接用group把這些程式碼區別開來沒什麼區別,反而要修改的時候還要重新git pull、pod install變得更加麻煩了。

因為這麼做被分離的程式碼無法獨立執行,而且由於依賴不清晰,沒辦法共享給其他專案使用,導致這種分離方式是一種偽解耦,該有的益處沒有展現出來,修改的時候反倒更加麻煩,這絕對不是我們想要的效果。

由此引出對模組獨立執行

對iOS模組獨立執行的思考

Pod帶來的受益的預期

個人心目中完美的Pod應該在這四個維度上做到最好。

  • 依賴足夠清晰 清楚的描述自己的依賴狀況。說到這個不得不吐槽一個iOS專案與Cocoapods結合之後一個奇怪的現象,就是Pod如果在主工程中通過path引入的,那麼,在不宣告清楚自身以來的情況下,可以使用主工程內所有類,甚至是主工程的類,並且不會得到任何提示,這種情況很普遍,並且事後想要理清依賴的成本極高,這個Pod算是廢了(沒有了抽離Pod的意義了)。

  • 方便共享給其他專案使用 我的理解,抽離Pod的很大一個目的不就是為了共享嗎?如果不是為了這個目的,其實沒必要抽離Pod的,反而更加麻煩了,只跟一個專案綁死的Pod在我看來,完全沒有必要做成Pod,專案裡放在Group就可以了。而為了能達到這個目的需要克服很多的困難。

  • 方便快速修改驗證 隨著主工程越來越大,編譯速度越來越慢,開發效率也在無形之中慢慢降低。Pod從主工程中脫離出來獨立執行,單獨編譯,隔絕對主工程的依賴,完全自給自足,這樣程式碼編譯量就會大大降低,達到開發效能提高的目的。

  • 自身質量保障 在第三點的基礎上,帶上完全針對這個Pod的單元測試UI測試,完美顆粒化程式碼的同時,還能很好的保障自身的質量,並且清晰易維護,這塊如果配合Xcode Server,會發揮強大的優勢。

然而要做到這些維度上的最好,需要克服很多問題,接下來慢慢道來。

兩種型別的程式碼

類似AFNetworking、SDWebImage這樣的功能型程式碼,分離的一個只需要確保自己的依賴清晰,被依賴的時候使用方便就可以了。而對於偏業務型的程式碼就不那麼容易了,通常會有介面,還會有各種業務帶來的附加產物,例如打點,例如網路庫各種規則。

因為功能型程式碼本身對於以上四點門檻不高,我就不展開討論了,主要還是展開說一下業務型程式碼。

業務型程式碼獨立執行的看法

推進業務型程式碼獨立執行過程中遇到的一些問題列舉:

獨立執行問題

  1. 業務程式碼需要用到 打點、網路等基本能力,背後每個能力都可能牽扯出一堆間接依賴,但這些依賴跟這個Pod本身沒任何屁關係,同時還會是不是的出現間接依賴不明確導致的編譯報錯問題。

  2. 作為已經是獨立可執行的Pod了,介面什麼的都自己hold了,那麼它一定還需要跟其他本身之外的幾面進行互動,舉個例子,一個Product的Pod,需要跳轉到Order中的一個介面,或者Chat中的一個介面,而這個介面在程式碼層面根本不存在,要如何處置。

  3. 之前也提到了,一個依賴清晰的獨立執行Pod如果被不小心path方式開發了一次,那麼這個Pod會慢慢變廢,下次執行可能就不能執行了,所以還要想辦法要怎麼不被path依賴。

  4. 還有一個比較頭疼的問題是,隨著業務迭代,某個冷門的獨立執行Pod並沒有跟上腳步,其直接依賴的功能庫在主工程都更新了,但它卻全然不知,難道還要一個一個校對嗎?

  5. 解決了基礎能力的間接依賴,各種必要的直接依賴的間接依賴也會出現不明確而出現的編譯失敗問題,需要解決,確保只有程式碼api需要更新時才會編譯不過,才是最爽的開發流程。

思考實施解決之道

實施過程中開發了兩套工具來解決。均未開源,外網勿搜。下面介紹詳細思路。

腳手架工具 - gearmaker

避免被path模式開發

好不容建了一個獨立執行Pod,要是不小心被不明真相的同學用path開發了就糟了。如何避免被path模式開發呢?如果是原始碼模式下,其實是做不到的。所以我們把每個獨立執行的Pod的產物定位二進位制庫,靜態動態都可,podspec層面就不允許指向原始碼,想要修改原始碼,只能通過獨立執行的工程進行修改。

具體操作這裡不展開了,如果podspec指向的是靜態庫,而沒有原始碼指向則這個Pod理論上不可能不可被path模式開發。

而如果通過Cocoapods 官方建議的 pod lib create 方式建立,則Pod程式碼會存在於 Development Pods 下,而必須在podspec中指定原始碼路徑,因此我修改了 pod lib create 的腳手架模板,將Pod程式碼直接放入專案的一個Group中,而Group對應產物是一個framework,podspec直接指向podspec,外加Universal打包指令碼就可以啦。

有同學有疑問了,那原始碼除錯怎麼辦呢?這個不用擔心,既然Pod已經可以獨立執行,有什麼問題需要除錯,是都可以在Pod工程中進行資料Mock來還原問題的,所以主工程只是用來整合,不需要考慮除錯問題。

即便是特別特殊的情況,只在主工程能還原,那臨時加一下podspec指向本地path做一下debug也是可以的。

版本仲裁 & 保鮮

Pod獨立執行工程的一大詬病就是時間一長,工程就無法編譯通過執行了,並且哪些依賴需要更新,需要詳細對比,成本非常高,很多遇到這樣情況就會放棄Pod工程。

為此gearmaker中整合了版本仲裁能力,通過hook pod命令,在pod install之前,計算出指定客戶端主工程最新的依賴全集,在pod install時,在這個全集中找到仲裁版本來使用。

這樣一來,pod install後的Pod工程所有依賴必然與主工程一致,只需要修改因為依賴更新帶來的相關api變更即可通過編譯正常執行,確保Pod工程不會腐敗。

依賴切斷 服務提供元件 - ServiceProvider

所有Pod只需要直接依賴ServiceProvider,由ServiceProvider來統一提供服務能力,包括Pod工程本身需要的任何能力。比如,路由、打點、網路、從另一個模組獲取資料,獲取一個View物件等,均不需要依賴其他庫,直接從ServiceProvider中通過內建的protocol來獲得,並使用。

提供能力的一方對預先放置在ServiceProvider中的protocol進行功能實現,通過以下方式將自身的能力註冊入ServiceProvider,即可為其他提供能力,而不需要依賴。

註冊服務

[ServiceProvider registService:[XMUserTrack class] withProtocol:@protocol(UserTrack)];
複製程式碼

獲取服務

id<UserTrack> ut = [ServiceProvider serviceWithProtocol:@protocol(UserTrack)];
複製程式碼

使用體驗

對於新的Pod建立,直接使用以下命令建立:

gearmaker <PodName>
複製程式碼

根據命令列提示進行建立即可。建立完成的腳手架直接提供了ServiceProvider,開發同學直接從ServiceProvider中獲取服務進行Pod開發,開發完成後通過Universal指令碼生成framework上傳到私有repo中定版本即可直接使用。

相關文章