原文鏈接
http://www.cocoachina.com/ios/20161103/17938.html?utm_source=tuicool&utm_medium=referral
業(yè)務(wù)背景
豆瓣在 2014 年聚合了移動端業(yè)務(wù),推出了一款叫“豆瓣”的App。隨著豆瓣App 的發(fā)展,豆瓣越來越多的業(yè)務(wù)線被納入其中。豆瓣App 代碼量越來越多,功能越來越復(fù)雜,體積越來越龐大。為了更從容地應(yīng)對這種狀況,使整個項目更健康,我們實施了模塊化。模塊化的最終目的是獨立出幾個業(yè)務(wù)模塊,使得各個業(yè)務(wù)模塊互不干擾,可以獨立開發(fā)。但其實在當前的豆瓣,豆瓣App 的開發(fā)仍然由是一個團隊負責,并沒有以業(yè)務(wù)線劃分工程團隊。所以,業(yè)務(wù)模塊的獨立并不是一個對應(yīng)公司組織構(gòu)架上的工程要求。而是我們基于項目發(fā)展健康做出的考慮。雖然,整個過程伴隨著一些痛苦。但結(jié)果還是不錯的,最后我們得到了以下好處:
- 一個更清晰的項目結(jié)構(gòu);
- 一些可以復(fù)用的公共組件;
- 幾個相互隔離的業(yè)務(wù)模塊。
開源成果
我們在模塊化過程中,也產(chǎn)出了一些庫和工具:
- FRDIntent,處理頁面間跳轉(zhuǎn)的庫。
- FRDModuleManager,簡單的模塊管理庫。
- Rexxar,移動混合開發(fā)框架。
我們將這些工具開源。一方面,是為了給大家提供一些借鑒的方向;另一方面,也是為了提高項目本身的質(zhì)量。我們知道還存在不少問題。所以,會悉心接受大家的意見和建議。
工程環(huán)境
在描述豆瓣的模塊化實踐之前,先簡單介紹一下豆瓣的移動開發(fā)中相關(guān)的工作環(huán)境。在 iOS 開發(fā)中,我們版本管理工具是 Git。公司所有的項目代碼都托管在內(nèi)網(wǎng)搭建的 github enterprise 上。github enterprise 的使用體驗和 github.com 基本一致。這受到了工程師的普遍歡迎。我們管理項目依賴的工具是 Cocoapods。Git 和 Cocoapods 這兩項現(xiàn)在基本上業(yè)界的事實標準。本文中,也只有這兩個工具和模塊化過程有關(guān)。
模塊化的實施
項目結(jié)構(gòu)
我們首先刻畫了模塊化之后最終我們希望得到的項目結(jié)構(gòu)。然后向著這個目標,一步步靠近。

- Frodo, 是豆瓣App 的項目名,豆瓣有使用魔戒人物命名項目的傳統(tǒng)。
- Leaf,需要獨立的業(yè)務(wù)模塊在沒有拆分之前都放在 Frodo 的 Leaf件夾下。
- Timeline,Group,Subjects,是三個拆分出來模塊。
- Fangorn,是 Frodo 的公共底層庫的項目名。包括了業(yè)務(wù)相關(guān)公共 UI 組件,數(shù)據(jù) Model,以及例如 FrodoRexxar 這樣的對外部庫的封裝。
- Library,是我們自己拆分出去的庫,或者第三方庫。后面會介紹到的 Rexxar,F(xiàn)RDIntent, FRDModuleManager 都屬于 Library。
實施步驟
模塊化是一個浩大的工程,對于項目有著重大影響。我們在確定目標之后,接著制定了有一個詳細的計劃。然后按計劃一步步實施。
我們把模塊化分為四個步驟,下面會分別介紹每個步驟。
文件夾隔離
我們首先需要改變項目的文件組織結(jié)構(gòu)。之前項目簡單地以 View / Controller / Model 劃分各類文件。現(xiàn)在改為首先按模塊劃分,每個模塊再劃分出自己 View / Controller / Model / Network 等文件夾。 比如,廣播 Timeline 有自己的 View / Controller / Model 文件夾;小組 Group 也有自己的 View / Controller / Model 文件夾。雖然改為按模塊組織項目的文件結(jié)構(gòu),但此時,所有模塊仍然還在一個倉庫里。這其實只是做到了文件夾隔離,代碼并沒有被真正隔離。我們會查看各個文件的 #import 部分,減少業(yè)務(wù)模塊間的相互依賴。幾個業(yè)務(wù)模塊都用到的文件,則會沉入到公共層。
這個階段是沒法做到徹底隔離的。因為,理清依賴的過程有賴于程序員自己查看代碼。文件位置看上去是分隔開了,但是由于還在一個倉庫里,代碼依賴肯定沒有辦法完全處理干凈。不過,沒有關(guān)系,文件夾隔離只是一個過渡階段。需要這樣一個過渡階段的原因是,產(chǎn)品開發(fā)不會因為我們要重構(gòu)項目就停止。文件夾隔離避免直接拆分一次性集中付出很長時間解決編譯錯誤。而使得團隊可以將處理依賴的時間分攤到每個版本中去。一個業(yè)務(wù)模塊在經(jīng)歷文件夾隔離階段之后,正真需要拆出去時所需要處理的未解決的依賴應(yīng)該已經(jīng)所剩不多了。拆分為獨立產(chǎn)品時的時間壓力就小了很多。
文件夾隔離也為團隊提供了一個轉(zhuǎn)變開發(fā)方式的緩沖期。業(yè)務(wù)模塊的劃分首先需要在全體組員間達成共識。從此,大家開始有了模塊化的意識。這表現(xiàn)為,新增文件會被放入對應(yīng)的模塊之中;code review 時會提出不應(yīng)該引用其他業(yè)務(wù)模塊的要求和建議。文件夾隔離使得組員逐步適應(yīng)模塊化的思維,后續(xù)的產(chǎn)品功能也被歸入到對應(yīng)的模塊之中。文件夾隔離,使產(chǎn)品開發(fā)和模塊化得以并行推進。在不影響產(chǎn)品開發(fā)進度的情況下,較從容地推進模塊化。
抽象出業(yè)務(wù)無關(guān)的庫
我們同時也鼓勵將一些業(yè)務(wù)無關(guān)的代碼抽象成一個個獨立的庫。這類庫應(yīng)該是與產(chǎn)品無關(guān),與業(yè)務(wù)無關(guān)的。這就意味著,它們在一定時期內(nèi)可以保持穩(wěn)定,不會隨著產(chǎn)品和業(yè)務(wù)的頻繁變化而變化。將這些底層邏輯抽象出來,拆分成一個個獨立的庫,有不少好處:
- 每個庫都有了自己清晰的邊界。未拆分之前各個庫的代碼混在項目中,就存在相互干擾的可能性。獨立出庫使得項目代碼中有了更多的隔離,項目質(zhì)量得到了提升;
- 拆分獨立的庫使得復(fù)用成為了可能,我們可以在新項目中使用它們,甚至將其開源,供其他開發(fā)者使用。FRDIntent, FRDModuleManager, Rexxar 都是這種情況;
- 為拆分出公共底層模塊 Fangorn 打下了基礎(chǔ)。這是因為,拆分出去的模塊,必須先處理好它的依賴,它將只能依賴已經(jīng)拆分出去的組件和第三方庫。
拆分出公共底層模塊
在 Frodo 中,公共底層模塊叫 Fangorn。Fangorn 包括了業(yè)務(wù)模塊所需要的一些公共代碼,但是要么是和業(yè)務(wù)關(guān)系較大,要么就是還沒到可以抽象成一個庫的程度。
在拆分獨立出很多業(yè)務(wù)無關(guān)的組件和庫之后。我們?nèi)匀挥幸徊糠执a是公共的,為多個業(yè)務(wù)模塊所使用的,但卻和業(yè)務(wù)有一定的關(guān)系。這部分代碼由于和業(yè)務(wù)相關(guān),拆分出去也沒有復(fù)用的可能。但卻是拆分業(yè)務(wù)模塊的前提條件。將 Fangorn 獨立為一個倉庫之后,我們才能著手業(yè)務(wù)模塊的拆分。
在拆分公共模塊時,有兩種方法:
- 一種是,將 Fangorn 劃分為一個個子模塊。將這些子模塊一個個拆分出去;
- 一種是,將 Fangorn 作為一個整體先摘出來。Fangorn 內(nèi)部各子模塊之間的依賴關(guān)系先按文件夾隔離的方式運作一段時間。如果,發(fā)現(xiàn)一個子模塊確實可以拆出去了就拆出去。
第一種方式更優(yōu)雅一些。如果完成,各個業(yè)務(wù)模塊可以選擇自己需要的依賴,而不是將 Fangorn 作為一個整體全部依賴。但是也更困難一些。因為,這需要花很長時間理清楚 Fangorn 各個子模塊之間的依賴關(guān)系。
第二種方式更簡單一些。將 Fangorn 作為一個整體摘出來,就只要處理好 Fangorn 和外部其他模塊的依賴關(guān)系。這個工作量就小很多了。
為了早一點將業(yè)務(wù)模塊獨立,我們選擇了第二種方式。
業(yè)務(wù)模塊獨立
在 Frodo 中,獨立的業(yè)務(wù)模塊在文件夾隔離階段都放在一個叫 Leaf 的文件夾下。我們的目標是拆出廣播 Timeline,小組 Group,條目 Subjects 三個模塊。
這一部分的工作是解除業(yè)務(wù)模塊之間的依賴。使得這三個模塊都只依賴 Fangorn,拆分出去的庫,和第三方庫。最終的目的是,這三個業(yè)務(wù)模塊獨立,并拆分到單獨的庫中。在經(jīng)歷了相當長時間的文件夾隔離,拆分出不少業(yè)務(wù)無關(guān)的庫,并獨立了 Fangorn 之后,我們開始按由容易到困難的順序拆分各個業(yè)務(wù)模塊。第一個待拆分的業(yè)務(wù)模塊是廣播 Timeline,而后會是小組 Group,再接著會是條目 Subjects。
最后我們?yōu)檫@三個業(yè)務(wù)模塊都單獨建立了可以運行的應(yīng)用 Demo 項目。這樣,它們就可以真正獨立開發(fā)地開發(fā)運行。我們的開發(fā)流程就變?yōu)椋合仍谶@三個模塊庫中開發(fā)新的產(chǎn)品功能,使用模塊自有的 Demo 查看結(jié)果。完成之后在主項目 Frodo 中升級三個業(yè)務(wù)模塊庫的版本,在 Frodo 中驗證測試集成效果。
FRDIntent: View controller 間的解耦
在 iOS 項目,存在大量頁面跳轉(zhuǎn)。但 iOS 系統(tǒng)并不存在像 Android 的 Intent 一樣的統(tǒng)一的頁面跳轉(zhuǎn)方法。在 iOS 中,處理頁面跳轉(zhuǎn),需要依賴代表跳轉(zhuǎn)目的頁面的類,需要知道它的初始化方法。這樣各個頁面就需要相互依賴。這種情況對解除耦合,拆分模塊很不利。為了解決這個問題,我們做了一個專門處理 iOS 中頁面跳轉(zhuǎn)的庫:FRDIntent,并將其開源。
Github地址:https://github.com/douban/FRDIntent
FRDIntent 有兩個部分:FRDIntent/Intent 用于解決應(yīng)用內(nèi)的頁面跳轉(zhuǎn);FRDIntent/URLRoutes,用于解決外部應(yīng)用對本應(yīng)用的頁面調(diào)用。在使用了 FRDIntent 之后,我們很好地解除了 view controller 之間的耦合。并且為內(nèi)部調(diào)用和外部調(diào)用提供了一套清晰統(tǒng)一的解決方案。解決了 iOS 項目中了很大一部分耦合問題:view controller 之間的耦合。為我們順利推進模塊化奠定了堅實基礎(chǔ)。
FRDIntent/Intent
FRDIntent/Intent 是一個消息傳遞對象,用于啟動 UIViewController??梢哉J為它是對 Android 系統(tǒng)中的 Intent 的模仿。當然,F(xiàn)RDIntent/Intent 做了極度簡化。這是因為 FRDIntent/Intent 的使用場景更為簡單:只處理應(yīng)用內(nèi)的 view controller 間跳轉(zhuǎn)。
直接使用 iOS 系統(tǒng)方法完成各 view controller 之間的跳轉(zhuǎn),各 view controller 代碼會耦合得很緊。跳轉(zhuǎn)時,一個 view controller 需要知道下一個 view controller 是如何創(chuàng)建的各種細節(jié)。這造成了 view controller 之間的依賴。使用 FRDIntent/Intent 傳遞 view controller 跳轉(zhuǎn)信息,可以解除 view controller 之間的代碼耦合。
FRDIntent/Intent 有如下優(yōu)勢:
- 充分解耦。調(diào)用者和被調(diào)用者完全隔離,調(diào)用者只需要依賴協(xié)議:FRDIntentReceivable。一個 UIViewControlller 符合該協(xié)議即可被啟動。
- 對于“啟動一個頁面,并從該頁面獲取結(jié)果”這種較普遍的需求提供了一個通用的解決方案。具體查看方法:startControllerForResult。這是對 Android 中 startActivityForResult 的模仿和簡化。
- 支持自定義轉(zhuǎn)場動畫。
- 支持傳遞復(fù)雜數(shù)據(jù)對象。
FRDIntent/URLRoutes
FRDIntent/URLRoutes 是一個 URL Router。通過 FRDIntent/URLRoutes 可以用 URL 調(diào)起一個注冊過的 block。
iOS 系統(tǒng)為各個應(yīng)用間的相互調(diào)用提供了一種基于 URL 的處理方案。即應(yīng)用可以聲明自己可以處理某些有特定 scheme 和 host 的 URL。其他應(yīng)用就可以通過調(diào)用這些 URL 而跳轉(zhuǎn)到該應(yīng)用的某些頁面。
FRDIntent/URLRoutes 是為了讓 iOS 系統(tǒng)中這種基于 URL 的應(yīng)用間調(diào)用的處理更為簡單。所以 FRDIntent/URLRoutes 和社區(qū)已經(jīng)存在的諸多 URL Routers 的功能和目的差別不大。FRDIntent 再次造了輪子是為了使 FRDIntent/URLRoutes 可以和 FRDIntent/Intent 配合一起解決應(yīng)用內(nèi)和應(yīng)用外的頁面調(diào)用。
FRDIntent/Intent 和 FRDIntent/URLRoutes
FRDIntent/URLRoutes 和 FRDIntent/Intent 可以配合使用。Intent 處理內(nèi)部頁面跳轉(zhuǎn);URLRoutes 負責來自外部的頁面調(diào)用。在 FRDIntent/URLRoutes 的實現(xiàn)中,F(xiàn)RDIntent/URLRoutes 只是起了暴露外部調(diào)用入口,接收外部調(diào)用的作用。在應(yīng)用內(nèi),仍然是通過 FRDIntent/Intent 啟動 view controller。
這么做在隔離了外部調(diào)用和內(nèi)部調(diào)用的同時,統(tǒng)一了外部調(diào)用和內(nèi)部調(diào)用的實現(xiàn)方法。外部調(diào)用最終使用內(nèi)部調(diào)用落地,是自然地復(fù)用代碼的結(jié)果。隔離外部和內(nèi)部調(diào)用則會帶來以下這些好處:
- iOS 系統(tǒng)提供的通過 URL 調(diào)用另外一個應(yīng)用功能本身就是使用在應(yīng)用之間的。iOS 系統(tǒng)中應(yīng)用之間的隔離是清晰而明確的,通過 URL 在應(yīng)用之間傳遞信息是合適的。但是,如果應(yīng)用內(nèi)部調(diào)用也使用 URL 傳遞信息,就會帶來諸多限制。Intent 更適合內(nèi)部調(diào)用的場景。通過 Intent,可以傳遞復(fù)雜數(shù)據(jù)對象,可以很容易地自定義轉(zhuǎn)場動畫。這些在 URL 方案中都很難做到。
- 區(qū)分了外部調(diào)用和內(nèi)部調(diào)用,我們就可以選擇是否要將一個內(nèi)部調(diào)用給暴露外部使用。這就避免了在 URL 的方案中,無法區(qū)分內(nèi)部調(diào)用和外部調(diào)用,將本應(yīng)只給內(nèi)部使用的調(diào)用也暴露給應(yīng)用外部了這種問題。
FRDModuleManager:為 AppDelegate 減負
在開發(fā)的過程中,我們發(fā)現(xiàn)項目中實現(xiàn)了 UIApplicationDelegate 協(xié)議的 AppDelegate 變得越來越臃腫。為了使 AppDelegate 更為健康,各模塊可以更容易地獲知應(yīng)用的生命周期事件。我們開發(fā)了一個簡單的模塊管理小工具:FRDModuleManager。這個小工具異常簡單,只有一個 .m 文件。我們也將其開源了。
FRDModuleManager 是一個簡單的 iOS 模塊管理工具。FRDModuleManager 可以減小 AppDelegate 的代碼量,把很多職責拆分至各個模塊中去。使得 AppDelegate 會變得容易維護。
如果你發(fā)現(xiàn)自己項目中實現(xiàn)了 UIApplicationDelegate 協(xié)議的 AppDelegate 變得越來越臃腫,你可能會需要這樣一個類似的小工具;或者如果你的項目實施了組件化或者模塊化,你需要為各個模塊在 UIApplicationDelegate 定義的各個方法中留下鉤子(hook),以便模塊可以知曉整個應(yīng)用的生命周期,你也可能會需要這樣一個小工具,以便更好地管理模塊在 UIApplicationDelegate 協(xié)議各個方法中留下的鉤子。
FRDModuleManager 可以使得留在 AppDelegate 的鉤子方法被統(tǒng)一管理。實現(xiàn)了 UIApplicationDelegate 協(xié)議的 AppDelegate 是我知曉應(yīng)用生命周期的重要途徑。如果某個模塊需要在應(yīng)用啟動時初始化,那么我們就需要在 AppDelegate 的application:didFinishLaunchingWithOptions: 調(diào)用一個該模塊的初始化方法。模塊多了,調(diào)用的初始化方法也會增多。最后,AppDelegate 會越來越臃腫。FRDModuleManager 提供了一個統(tǒng)一的接口,讓各模塊知曉應(yīng)用的生命周期。這樣將使 AppDelegate 得以簡化。
嚴格來說,AppDelegate 除了通知應(yīng)用生命周期之外就不應(yīng)該擔負其他的職責。對 AppDelegate 最常見的一種不太好的用法是,把全局變量掛在 AppDelegate 上。這樣就獲得了一個應(yīng)用內(nèi)可以使用的全局變量。如果你需要對項目實施模塊化的話,掛了太多全局變量的 AppDelegate 將會成為一個棘手的麻煩。因為,這樣的 AppDelegate 成為了一個依賴中心點。它依賴了很多模塊,這一點還不算是一個問題。但是,由于對全局變量的訪問需要通過 AppDelegate,這就意味著很多模塊也同時依賴著 AppDelegate,這就是一個大問題了。這是因為,AppDelegate 可以依賴要拆分出去的模塊;但反過來,要拆分出去的模塊卻不能依賴 AppDelegate。
這個問題,首先要將全局變量從 AppDelegate 上剔除。各個類應(yīng)該自己直接提供對其的全局訪問方法,最簡單的實現(xiàn)方法是將類實現(xiàn)為單例。變量也可以掛在一個能提供全局訪問的對象上。當然,這個對象不應(yīng)該是 AppDelegate。
其次,對于 AppDelegate 僅應(yīng)承擔的責任:提供應(yīng)用生命周期變化的通知。就可以通過使用 FRDModuleManager 更優(yōu)雅地解決。
這兩步之后,AppDelegate 的依賴問題可以很好地解決,使得 AppDelegate 不再是項目的依賴中心點。
Rexxar:混合開發(fā)
豆瓣在混合開發(fā)方面做了不少實踐工作。我們將這個過程的主要產(chǎn)出:Rexxar 開源了。這篇文章詳細介紹了 Rexxar。
我們推進混合開發(fā)的主要目的是提高工程效率。但同時也有一個副產(chǎn)物:由于使用 Rexxar 實現(xiàn)的頁面是使用 Web 技術(shù)實現(xiàn)整個業(yè)務(wù),所以,在效果上,實現(xiàn) Rexxar 頁面的 Web 代碼和項目其余的 Native 代碼是完全隔離的。我們在業(yè)務(wù)層使用的前端框架是 React。由于 React 本身組件化的特性,在前端代碼內(nèi)部,項目代碼的模塊化也做得不錯。
所以,從效果上看,混合開發(fā)在我們實施模塊化的過程中也起了作用。使用 Rexxar 開發(fā)的頁面除了 Rexxar 這個庫,不依賴于項目的其他 Native 代碼。
普通對象間的解耦
在處理頁面跳轉(zhuǎn)時使用 FRDIntent,已經(jīng)解決了大部分的模塊間調(diào)用問題。但這并沒有解決所有的耦合問題,項目中模塊間除了頁面跳轉(zhuǎn)之外,還會有其他對象間的相互依賴。除了跳轉(zhuǎn)到模塊內(nèi)某個頁面這種接口之外,一個模塊還需暴露某些對象或者數(shù)據(jù)的話,我們怎么處理這種依賴呢?例如,要暴露一個 UI 組件,或者一個計算結(jié)果。用具體的廣播 Timeline 模塊舉例:如果項目的其他模塊需要展示一條廣播,或者要知道某用戶發(fā)了多少條廣播這種計算結(jié)果。這些當然已經(jīng)在廣播模塊里實現(xiàn)過了。那么,我們?nèi)绾翁峁﹤€一條廣播這個 UI 組件,和用戶發(fā)了多少條廣播這個計算結(jié)果呢?我們首先采用了兩類稍顯簡單粗糙的方法:
- 如果它們真的完全一樣,而且多個模塊中用到了。那我們就將其沉入公共底層模塊 Fangorn。這樣各個模塊都依賴公共底層模塊,而不用相互依賴;
- 如果它們并不完全一樣,我們就可能會拷貝一份代碼。為了防止沖突,會重新命名類。由于實施了模塊化,一定程度的代碼冗余是應(yīng)該付出的代價,并可以忍受的。
除此之外,可能的解決方案是類似于 Java 社區(qū)中常用到的依賴注入容器。例如,Spring 就是一個著名的依賴注入容器。引入依賴注入容器之后,可以動態(tài)地創(chuàng)建對象,并為對象注入依賴。而所有依賴都在容器中注冊,業(yè)務(wù)對象只需要依賴接口,而無需依賴具體的類型。這是一種通用的解耦方法。適用于幾乎所有的依賴管理場景。但在我們的實踐中,考慮到避免復(fù)雜性,并沒有引入一個依賴注入容器。使用依賴注入容器意味著項目中很多對象的創(chuàng)建都需要交托給容器。這對于整個項目的代碼運作方式做了很大的改變。對于這類基礎(chǔ)性的改變,我們都會比較慎重。而且我們對于在移動平臺中,找到或者自己實現(xiàn)這樣的一個堅實的基礎(chǔ)容器也并不樂觀。
遇到的問題
Swift
我們在項目中使用了不少 Swift。這其中遇到了一些問題:
- 現(xiàn)階段 Swift 本身并不穩(wěn)定。Swift 2 和 Swift 3 較前一版本都有較大變化。雖然,有 Xcode 提供的自動轉(zhuǎn)換工具,但仍然需要投入精力檢查和測試。在一個大型項目中,安全和穩(wěn)定一般都是首要要求。引入一種處于發(fā)展過程中并不穩(wěn)定的語言對于項目質(zhì)量是一種威脅。但是,Swift 的情況稍有不同。對于 iOS 開發(fā),我們并沒有太多選擇。只有 Objective-C 和 Swift,而可以確定的是 Swift 會是未來,Objective-C 將會是歷史。那么,我們其實只能選擇何時,以何種方式切換到 Swift 而已。我們的經(jīng)驗是先小塊實驗,再謹慎推進。每次升級現(xiàn)有代碼的 Swift 版本都一小塊一小塊做,不一次性轉(zhuǎn)換所有的代碼。
- 使用包含 Swift 代碼的庫對 iOS 系統(tǒng)版本有要求。如果要使用一個包含了 Swift 代碼的庫,需要以動態(tài)庫的形式引入。動態(tài)庫對 iOS 的系統(tǒng)版本有要求:iOS 8 以及以上版本。我們在 iOS 10 發(fā)布時,放棄了對 iOS 7 的支持。這使得我們可以使用包含 Swift 代碼的庫。如果項目對于低版本 iOS 的支持有要求,那么現(xiàn)階段就不能使用包含 Swift 代碼的庫。通過 Cocoapods 將庫以動態(tài)庫形式使用的方法很簡單:開啟 Cocoapods 的 !use_framework標識即可。
Swift 在工程效率上確實優(yōu)于 Objective-C。和 Objective-C 相比,Swift 可以用更少的代碼,更清晰的方式完成相同的功能。當然,混合使用 Swift 和 Objective-C 存在一定的工程成本。所以,這里就需要權(quán)衡:是保持簡單,只使用 Objective-C 呢?還是忍受一定的不便,使用一些 Swift,帶來效率上的提升呢?我們在項目中使用 Swift 的體會是:有快樂,當然也伴隨著一些不便??傮w而言,不便都可以克服。
總結(jié)
相信我們在整個模塊化實踐過程中取得的經(jīng)驗,對于一個成長中的移動項目應(yīng)該會有某些借鑒意義。我們產(chǎn)出的一些開源庫和工具,也可能會對大家有幫助,或者啟發(fā)意義。也歡迎大家提出反饋和建議,幫助我們改進和提高。