iOS專案的目錄結構
清晰的專案目錄結構有利於專案的開發,同時也是軟體架構的一部分,所以,專案開發之初搭建專案的目錄結構很重要。
通常採用的目錄結構如下:
|—MyProject |—ignore-folder // 放置不想同步到程式碼伺服器上的內容,通常包括一些體積太大、經常變動、對專案執行影響不大的檔案。需要在該目錄下新增 .gitignore 對本目錄做一些設定。 |—readme.log // 因為 ignore-folder 目錄下的內容都是不會同步到程式碼伺服器上的,所以最好加一個 log 檔案記錄一下你在該目錄的操作。 |—3rdparty // 比如,一些不能用 CocoaPods 管理也不想同步到程式碼伺服器上的第三方庫。|—data // 比如,一些經常會變動的、自己的測試資料檔案。 |—Utility // 自己實現的一些通用性較好的功能程式碼,這些程式碼有比較好的介面且與本專案不存在耦合,可直接複用於其他專案。 |—Common // 本專案的一些全域性性程式碼,這些程式碼通常與本專案的業務邏輯存在一些耦合,所以不放在 Utility 目錄中。 |—Feature // 本專案的功能模組目錄,該目錄下將專案的功能劃分為多個模組,每個模組穿透 MVC,可以獨立劃分出去。當然,在模組下你不採用 MVC,採用 MVVM 或其他架構方式也沒問題的。 |—Base // 定義本專案中各種 Controller、View、Model 的基礎類或基礎介面。|—Controller |—View |—Model |—Main |—Controller |—View |—Model |—User |—Controller |—View |—Model |—Resource // 本專案的資源目錄,放置圖片、音訊等資料。 |—Image |—Sound |—Pods // 採用 CocoaPods 管理的第三方庫。
目錄結構的進化
形式一
|—MyProject |—ignore-folder |—readme.log |—3rdparty |—data |—Utility |—Common |—Service |—LocalService // 封裝在 DAO 層之上,直接對接業務邏輯層,提供本地資料服務。 |—DAO // 封裝本地資料庫訪問層的相關程式碼。 |—WebService // 對接業務邏輯層,提供網路資料服務。 |—Model // 封裝專案中的實體類。 |—View |—Controller |—Resource |—Image |—Sound |—Pods
上面的目錄結構主要特點是基於 MVC 的架構構建的。Model 層主要封裝一些資料物件實體,它們的例項將在專案的資料流中流動;View 層主要放一些控制元件,被 Controller 層用來展示;Controller 層就是一些 ViewController,獲得資料 Model,用 View 展示出來;Service 層則為 Controller 層提供本地或網路的資料服務。
形式二
下面的目錄結構就是文章開頭所說的我現在通常採用的目錄結構。這種形式的主要特點就是更好地支援基於 Feature 進行模組劃分和任務指派。在每個 Feature 下,對應的開發人員需要穿透 MVC 整個層次來完成這個功能模組的開發,那麼他對於各層的介面也能更高效地開發,不需要的介面不用寫,複雜的介面能寫的更高效。甚至,開發人員可以根據 Feature 的特點採用更適合的架構,比如 MVVM 架構等等,這樣更具靈活性。但是需要關注的是,還是需要提供一定規範限制,保持每個 Feature 下程式碼結構的清晰,這樣也利於同事的查閱、修改和呼叫。
|—MyProject |—ignore-folder |—readme.log |—3rdparty |—data |—Utility |—Common |—Feature |—Base |—Controller |—View |—Model |—Main |—Controller |—View |—Model |—User |—Controller |—View |—Model |—Resource |—Image |—Sound |—Pods
基本原則
上面的 iOS 專案目錄結構不一定適合所有人的想法,關鍵看你希望用一個結構解決什麼問題。就我自己而言,我是希望通過一個良好的專案結構去達到兩個目的:
- 1)使專案更適合於團隊開發,能夠降低耦合、便於任務的劃分和程式碼的整合管理。
- 2)使專案能夠積累出更多可複用的程式碼和架構。
這個結構會在不斷遇到問題解決問題的過程中權衡、進化,在這個過程最重要的是能夠保持:
- 1)主幹簡潔。主幹上防止過度劃分,過度劃分會讓程式碼放在這個目錄下也可以,放在另一個目錄下好像也行,容易混亂。
- 2)分支開放。不對過於細節的分支做嚴格規範,可以發揮大家的靈活性和創造性。
關於Xcode的資料夾
Group 和 Folder Reference 的區別
說完目錄結構,插一點小話題,說說 Xcode 的資料夾。Xcode 專案的資料夾有 Group 和 Folder Reference 之分。它們的區別在Xcode Groups vs. Folder References這篇文章裡有詳細的講述。
Group 的缺點如下:
- 1)Xcode 會為每個檔案建立一個 reference,儲存在 project.pbxproj 檔案中,當有多個 target 時,每個檔案的 reference 會被複制多個,這就會大大增加 project.pbxproj 檔案的尺寸和複雜度,這對於程式碼版本管理來是個頭疼的問題,尤其是遇到 merge conflict 的時候。
- 2)專案中 Group 的結構跟磁碟上的檔案價的結構可以說是沒有啥關係的,在磁碟上的一個檔案在一個某個資料夾中,但是在 Xcode 的專案結構中,它可能在任何 Group 中,這樣想要去找對應的檔案就常常讓人很暈。
- 3)如果你在 Xcode 之外直接去 Finder 裡移動專案檔案到不同的目錄時,那麼在 Xcode 中對這個檔案的 reference 就會壞掉。
Group 的優點如下:
- 1)你可以選擇磁碟上的檔案新增到專案中,不想要的不新增就行了。
- 2)對不同的 target 對應的檔案能夠更好地管理,比如,你可以選擇對一個 target 排除某一個檔案。
- 3)當 build 的時候,Xcode 會把所有的 Group 下的檔案都放到 bundle 的頂級目錄,所以你呼叫檔案時不需要制定它的具體位置,比如,你不必這樣
[UIImage imageNamed:@"Images/Icons/Go"];
,這用這樣就可以[UIImage imageNamed:@"Go"];
,但是這就意味著在整個專案中,你不能有同名的檔案了。
Folder Reference 的有這些優點:
- 1)Xcode 只儲存 folder 的 reference,這個 folder 下所有的檔案和 subfolder 都會自動新增到專案中去。這會使專案檔案更小更簡單,程式碼版本管理時,產生 merge conflict 的可能就更少。
- 2)如果你在檔案系統中直接對 folder 下的檔案進行修改、移動或者刪除,Xcode 會自動更新對應的 folder reference 來反應這些改變,這樣管理專案檔案也更簡單。
- 3)專案的結構和 folder 在磁碟的結構是一致的,這樣就不會暈菜了。
- 4)由於存在不同的 folder 路徑,你就不需要擔心檔案重名問題了,因為在 build 的時候,資料夾結構也會被放到 bundle 中去。
Folder Reference 的有這些缺點:
- 1)對不同的 target 的管理是個災難,因為一個 folder 下的程式碼或檔案,要麼全一樣要麼全不要。當然,如果你能為不同的 target 去建立不同的 Folder Reference,這看起來也沒什麼不好的。
- 2)對 folder 下的檔案無法隱藏,磁碟上如果這個 folder 下有這個檔案,那麼在專案結構中就會看到它。
- 3)在載入檔案資源的時候,你必須制定全路徑。也就是說,你得這樣:
[UIImage imageNamed:@"Images/Icons/Go"];
。 - 4)儲存在 Folder Reference 中的圖片在 Interface Builder 中使用時會遇到各種問題。
在實際使用中,使用 Group 要多得多。
Xcode 專案結構和磁碟檔案結構的對應
上面說了 Group 和 Folder Reference 各自的優缺點,在專案中,我習慣上也是不使用 Folder Reference,只用 Group。在 Xcode 專案中建立 Group 的方式有兩種:
- 1)建立時需要先在磁碟上建立對應的資料夾,再把資料夾拖進 Xcode 專案中對應的位置,並選擇 Create groups for any added folders。這樣建立的 Group 會對應著磁碟上的 Folder。
- 2)直接在 Xcode 專案中建立 Group。
對照 Xcode 專案的 MyProject.xcodeproj/project.pbxproj 檔案可以看到對應著 Folder 的 Group 和直接建立的 Group 的區別就在於前者是用 path 屬性去記錄,後者是用 name 屬性去記錄。如下,WebService 是一個不對應 Folder 的 Group,Resource 是一個對應 Folder 的 Group。通過各個結點的父子關係以及 path 屬性,Xcode 就能管理好每個檔案的 Reference。
對於第一種方式:
好處
:這樣磁碟上的結構和 Xcode 中的專案結構就是一一對應的。讓 Xcode 的目錄結構能夠跟磁碟檔案的結構保持一致,這樣讓在找程式碼檔案的時候能夠更清晰。壞處
:建立和管理程式碼檔案時就要麻煩一些。當你在 Xcode 專案中把一個檔案從一個目錄拖動到另外一個目錄中時,它在 Xcode 中顯示的目錄路徑改變了,但是它在磁碟上的物理位置並沒有發生改變,這就會造成混亂。所以,為了保持對應關係,當你要改變檔案的目錄時,你需要手動到 Finder 資料夾中去移動檔案,再在 Xcode 專案中去刪除相應的檔案引用再重新新增到新的目錄下。這又帶來了另外一個問題,就是 git 對檔案的版本管理資訊會被破壞,你可能無法看到這個檔案之前的版本了,這個代價可就大了。所以,如果要採用版本管理,就要好好考慮這個因素了。
對於第二種方式:
好處
:直接在 Xcode 中管理專案,新增、刪除、移動都很方便。採用版本管理時,程式碼檔案的 版本資訊不會因為移動而丟失。壞處
:Xcode 中的專案結構和磁碟上的結構不能一一對應。
根據上面的對比,
推薦
:對於固定的可複用程式碼可以採用第一種方式,因為也不會經常移動。複用時,挪動起來也好操作;對於大的目錄結構,比如一級目錄:Utility、Common、Feature、Resource 等目錄可以採用第一種方式。其他的情況均採用第二種方式就行了。