1. 程式人生 > 其它 >iOS專案的目錄結構

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 的資料夾。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 要多得多。

上面說了 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 等目錄可以採用第一種方式。其他的情況均採用第二種方式就行了。

原文