iOS 開發:深入理解 Xcode 工程結構(一)
作者 | 錢凱
杏仁移動開發工程師,前嵌入式工程師,關注大前端技術新潮流。
當我們新建一個 Cocoa 項目時,Xcode 會提供一系列的模板,類似前端的腳手架工具,只需要簡單的幾個選項,就可以配置好一個項目所需的基本環境。這些基本環境配置一般包括:
- 編譯選項、證書鏈選項
- 項目 Target、單元測試 Target
- 基於 git 的版本控制管理
- 默認的源文件
當然我們也可以新建一個空白的 Project,然後手動去組裝這些東西。
由於蘋果的封閉性,對 Cocoa 項目的管理基本上都在 Xcode 中進行,這個 All-in-One 的強大工具提供了從文檔、編碼、調試、測試,再到簽名、打包、上線的全流程支持。
隨著開發的深入,我們的項目變得越來越複雜,各種鏈接庫、子工程相互引用,不同 Target、Scheme 配置混雜,還會遇到多人協作開發時詭異的衝突。即使是老鳥有時候也會一籌莫展,這時候就需要對 Xcode 工程結構以及管理機制有更加清晰的認識。
Scheme、Target、Project 和 Workspace
Schema、Target、Project 和 Workspace 是組成一個 Xcode 工程最核心的單元,也是我們首先需要理解的部分。
Target
Target 是我們工程中的最小可編譯單元,每一個 target 對應一個編譯輸出,這個輸出可以是一個鏈接庫,一個可執行文件或者一個資源包。它定義了這個輸出怎樣被 build 的所有細節,包括:
- 編譯選項,比如使用的編譯器,目標平台,flag,頭文件搜索路徑等等。
- 哪些源碼或者資源文件會被編譯打包,哪些靜態庫、動態庫會被鏈接。
- build 時的前置依賴、執行的腳本文件。
- build 生成目標的簽名、Capabilities 等屬性。
我們平時在 Build Settings,Build Phases 中配置的各種選項,大部分都是對應到指定的 target 的。
每次我們在 Xcode 中 run/test/profile/analyze/archive 時,都必須指定一個 target。
工程中的 targets 有時候會共享很多代碼、資源,這些相似的 targets 可能對應同一個應用的不同版本,比如 iPad 版和 iPhone 版,或者針對不同市場的版本。
Project
Project 很好理解,就是一個 Xcode 工程,它管理這個工程下的 targets 集合以及它們的源碼,引用的資源,framework 等等。
Project 是管理資源的容器,本身是無法被編譯的,所以每個 project 至少應該有一個可編譯的 target,否則就是一個空殼。一個 target 編譯時引用的資源是它所在 project 所有管理資源的子集。
我們也可以對 project 進行配置,包括基本信息和編譯選項(Build Settings)等,這些配置會應用到它管理的所有 targets 中,但是如果 target 有自己的配置,則會覆蓋 project 中對應的配置。
在很多情況下,我們的工程中只有一個 project。可以在 finder 中雙擊後綴名為.xcodeproj
的文件,就可以直接打開單個 project 了。
如果我們需要從源碼編譯一個依賴庫,可以把這些源碼所在的工程作為主工程的subProject
添加到目錄結構中去:
然後將這個子工程的某個 target 作為主工程 target 的依賴,從而在 build 主工程 target 的時候,順便也會編譯子工程對應的 target。
這樣做的好處是你可以在一個窗口中同時修改主工程和子工程的源碼,並且一起進行編譯。
Workspace
上面所說的添加子工程的方法,已經很好的解決了不同項目中 target 依賴的問題了,那麼什麼時候需要用到 Workspace 呢?
當一個 target 被多個不同的項目依賴,或者 project 之間互相引用,那麼我們就需要把這些 projects 放到相同的層級上來。管理相同層級 projects 的容器就是 Workspace。
和 projects,target 不同,workspace 是純粹的容器,不參與任何編譯鏈接過程,它主要管理:
- Xcode 中的 projects,記錄它們在 Finder 中的引用位置。
- 一些用戶界面的自定義信息(窗口的位置,順序,偏好等等)。
注意到,當你把不同的 projects 放到一個 workspace 中管理後,你仍然可以用 Xcode 單獨打開其中的某一個 project,但是如果它涉及到對其它 project target 的依賴,這時候你無法在這個單獨的窗口中編譯成功。
在 iOS 開發中,我們常常使用 Cocoapods 來管理三方庫,它會把這些三方庫的源碼組裝成一個 project,和主工程一起放入到 workspace 中,自動配置好主工程與 pods 庫之間的依賴。所以如果引入了 Cocoapods,我們必須打開這個新的 workspace 才能正常 build 原來的項目。關於 Cocoapods,我們在後面的文章中再詳細介紹
Scheme
日常開發中我們常常點擊 Xcode 左上角的 Run 箭頭來運行調試代碼,這其實就是執行了 Scheme 定義的一個任務。
針對一個指定的 target,scheme 定義了 build 這個 target 時使用的配置選項,執行的任務,環境參數等等。Scheme 可以理解為一個工作流,或者藍圖,當我們點擊 debug,test 按鈕時,Xcode 會按照 scheme 中的定義,去執行對應的工作流。
Scheme 中預設了六個主要的工作流: Build, Run, Test, Profile, Analyze, Archive。包括了我們對某個 target 的所有操作,每一個工作流都可以單獨配置。
Scheme 中最重要的一個配置是選擇 target 的 build configuration,對每一個 project,會有兩個默認的 build configuration:debug 和 release。
每個 configuration 對應了 build target 時不同的參數集,比如宏,編譯器選項,bundle name 等等。我們可以在 target 的配置頁中更改這些選擇項,也可以自己創建新的 build configuration,比如為 App 創建免費和付費版本的配置。
除了 build configuration 外,scheme 還可以配置:
- 運行時的環境變數(Environment Variables)
- 啟動時設置給 UserDefaults 的參數(Arguments Passed on Launch)
- App 執行時的系統語言、模擬的定位坐標、國家等環境參數
- runtime,內存管理,日誌,幀率檢測等調試選項
另外有一些 debug 時十分有用的選項,也可以在 scheme 配置中找到。
一個 scheme 對應一個 target,同一個 target 可以有多個 scheme,通過靈活地配置 scheme,我們可以方便地管理不同環境下 App 的測試,調試,打包流程。
下表列舉了我們在 Scheme 中的常見配置選項:
選項說明Pre(Post)-actions指定在進行工作流之前(之後)執行的腳本,發送郵件通知等任務。Launch編譯完成後是否立即運行Arguments Passed On Launch指定一些運行時的參數,比如本地化語言的選項,Core Data 調試選項等Environment Variables指定環境變數,比如開啟殭屍內存,Malloc選項,I/O buffer大小等。Application Language/RegionApp 運行使用的語言和國家XPC Services打開調試 XPC (應用間通信)Queue Debugging打開線程調試,會自動記錄運行時的線程信息。Runtime Sanitization是否打開運行時的一些調試選項,包括內存檢測、多線程檢測等等,在 debug 一些棘手的異常時十分有用Memory Management開啟一些內存管理相關的服務,包括內存塗抹,邊緣保護,動態內存分配保護,殭屍對象等等Logging配置調試過程中終端輸出的日誌Runtime Sanitization是否打開運行時的一些調試選項,包括內存檢測、多線程檢測等等,在 debug 棘手的異常時十分有用
本文參考:- Xcode Concepts
- Xcode and git: bridging the gap
- Xcode Project vs. Xcode Workspace - Differences
- limit run script build phase to release configuration
- Xcode - xcworkspace and xcodeproj
- Launch Arguments & Environment Variables
全文完
我們正在招聘 Java 工程師,歡迎有興趣的同學投遞簡歷到 rd-hr@xingren.com 。
歡迎搜索關注微信公眾號:杏仁技術站(微信號 xingren-tech)。推薦閱讀: