[iOS]XcodeProject的內(nèi)部結(jié)構(gòu)分析

1. 背景

平時(shí)開發(fā)中,經(jīng)常會(huì)遇到xcodeproj沖突,就需要打開這個(gè)文件,進(jìn)行處理。當(dāng)然現(xiàn)在也有很多工具或者自動(dòng)化的腳本來自動(dòng)merge,比如 simonwagner/mergepbx, 但這個(gè)文件錯(cuò)綜復(fù)雜,尤其當(dāng)項(xiàng)目大到一定階段后,非常可怕,所以很多情況還是需要人工來處理各種沖突。

所以決定研究下,這個(gè)龐然大物內(nèi)部的結(jié)構(gòu)究竟是怎樣的...

2. XcodeProj的工程結(jié)構(gòu)

2.1 project.pbxproj文件

Xcode每個(gè)項(xiàng)目的工程文件都在xxx.xcodeproj中,查看包內(nèi)容,可以看到真正的內(nèi)容都在project.pbxproj里面,其他有一些xcuserdata之類的,不重要,先忽略。我們主要來看pbxproj文件。

這是一個(gè)plist文件。記錄了所有代碼的和庫(kù)文件的索引和路徑信息,以及Target信息,包括Build Setting/Build Phase等等信息。

雖然xcodeproj提供了很多方便的文件管理和索引,但我自身還是更喜歡無project文件的代碼,直接與物理目錄對(duì)應(yīng),不要與IDE產(chǎn)生太多的依賴和耦合。

2.2 pbxproj預(yù)覽

一個(gè)完整的pbxproj文件基本是如下這樣:

// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 46;
    objects = {
        …
    };
    rootObject = 4B74E19C1AB185A200A5A377 /* Project object */;
}

核心內(nèi)容都在objects中,完整版如下:

// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 46;
    objects = {
        /* 構(gòu)建所需的代碼文件,資源文件,庫(kù)文件等 */
        /* 平時(shí)git發(fā)生沖突也主要是在這個(gè)區(qū)域內(nèi)沖突 */
        /* 你每新建一個(gè).h/.m文件,就會(huì)修改這個(gè)區(qū)域, 各個(gè)branch都在創(chuàng)建的時(shí)候,容易沖突 */
        /* Begin PBXBuildFile section */
            ...
        /* End PBXBuildFile section */
        
        /* 這里記錄了每個(gè)target的targetProxy,與PBXTargetDependency相對(duì)應(yīng) */
        /* Begin PBXContainerItemProxy  section */
            ...
        /* End PBXContainerItemProxy section */
        
        /* 主要記錄每個(gè)target的BuildPhase中的Embed App Extensions的部分 */
        /* Begin PBXCopyFilesBuildPhase section */
        /* End PBXCopyFilesBuildPhase section */

        /* 記錄了每個(gè)代碼文件的文件類型、路徑path、sourceTree,不論引入文件的時(shí)候是create group還是create reference,都會(huì)在這里添加一條記錄 */
        /* Begin PBXFileReference section */
        /* End PBXFileReference section */

        /* 工程中所依賴的Frameworks的信息,對(duì)應(yīng)Build Phases中的`Link Binary With Libraries` */
        /* Begin PBXFrameworksBuildPhase section */
        /* End PBXFrameworksBuildPhase section */

        /* 工程中所有文件的group信息,這個(gè)和xcode文件目錄是對(duì)應(yīng)的,每一層的文件目錄有唯一的UUID,同一層group下的子group會(huì)和上一層的group的UUID有很高的重合度(基本只有1-2位不同),這個(gè)PBXGroup section中,子group沒有用樹的方式,而是采用類似列表的方式呈現(xiàn)了所有的group目錄,可以腦補(bǔ):打開xcode左側(cè)目錄,然后讓所有目錄和文件"左對(duì)齊",然后就會(huì)生成如下的結(jié)構(gòu)` */
        /* Begin PBXGroup section */ 
        /* End PBXGroup section */

        /* 每個(gè)Target的BuildSettings和BuildPhases(Sources/Frameworks/Resources等)的信息 */
        /* Begin PBXNativeTarget section */  
        /* End PBXNativeTarget section */

        /* 整個(gè)項(xiàng)目工程Project的信息,包括項(xiàng)目路徑、Config信息,相關(guān)版本號(hào),所有的Target等信息 */
        /* Begin PBXProject section */
        4B74E19C1AB185A200A5A377 /* Project object */ = {
            isa = PBXProject;
            attributes = { ... };
            ...
            targets = (
                4B74E1A31AB185A200A5A377 /* xxxxPolenTestxxxx */,
                ...
            );
        };
        /* End PBXProject section */

        /* 列舉了項(xiàng)目中每個(gè)Resources的信息, 包括Build Phase下`Copy Bundle Resources`文件、Assets.xcassets等資源文件 */
        /* Begin PBXResourcesBuildPhase section */
        /* End PBXResourcesBuildPhase section */

        /* 對(duì)應(yīng)Xcode中Build Phases下的腳本文件,包括:Embed Pods Frameworks,Check Pods Manifest.lock以及其他本地或者第三方的腳本文件信息 */
        /* Begin PBXShellScriptBuildPhase section */
        /* End PBXShellScriptBuildPhase section */

        /* 對(duì)應(yīng)Xcode中Build Phases的Complie Sources的代碼文件 */
        /* Begin PBXSourcesBuildPhase section */
        /* End PBXSourcesBuildPhase section */

        /* 記錄了每個(gè)Target的targetProxy,每個(gè)targetProxy都是一個(gè)PBXContainerItemProxy類型,暫時(shí)沒找到Xcode中的對(duì)應(yīng)項(xiàng) */
        /* Begin PBXTargetDependency section */
        /* End PBXTargetDependency section */

        /* 不同地區(qū)的資源文件的引用信息,如果你項(xiàng)目使用了國(guó)際化,相關(guān)的xxx.string就在這個(gè)section中 */
        /* Begin PBXVariantGroup section */
        /* End PBXVariantGroup section */

        /* 對(duì)應(yīng)Xcode中 Build Settings中的配置信息 */
        /* Begin XCBuildConfiguration section */
        /* End XCBuildConfiguration section */

        /* XCBuildConfiguration只是列舉了所有Target的所有Setting項(xiàng),下面這個(gè)文件區(qū)分,不同Target在Debug時(shí)使用哪個(gè)Setting項(xiàng),在Release時(shí)使用哪個(gè)Setting項(xiàng) */
        /* Begin XCConfigurationList section */
        /* End XCConfigurationList section */
    };
    rootObject = 4B74E19C1AB185A200A5A377 /* Project object */;
}

2.3 pbxproj結(jié)構(gòu)

2.3.1 結(jié)構(gòu)圖

首先可以去看一下Xcode Project File Format , 很詳細(xì)的介紹了pbxproj中每個(gè)類和及其屬性字段的含義和引用關(guān)系。

從Plist的角度看,我們可以將PBXProject看成一個(gè)個(gè)節(jié)點(diǎn)和子節(jié)點(diǎn)的樹形結(jié)構(gòu),但從面向?qū)ο蟮慕嵌?,其?shí)就是一個(gè)個(gè)類和子類。

自己簡(jiǎn)單整理了一下pbxproj的結(jié)構(gòu)圖 (原創(chuàng)):

xcodeproject_2018-08-06_23.png

2.3.2 Class Hierarchy

下面具體說一下每個(gè)節(jié)點(diǎn)/類 模塊包含的內(nèi)容以及在Xcode中對(duì)應(yīng)哪些文件或者目錄:

  • PBXBuildFile: 構(gòu)建所需的代碼文件,資源文件,庫(kù)文件等
  • PBXBuildPhase: 對(duì)應(yīng)Xcode中Build Phases
    • PBXAppleScriptBuildPhase
    • PBXCopyFilesBuildPhase: 主要記錄每個(gè)target的BuildPhase中的Embed App Extensions的部分
    • PBXFrameworksBuildPhase: 工程中所依賴的Frameworks的信息,對(duì)應(yīng)Build Phases中的Link Binary With Libraries
    • PBXHeadersBuildPhase
    • PBXResourcesBuildPhase: 列舉了項(xiàng)目中每個(gè)Resources的信息, 包括Build Phase下Copy Bundle Resources文件、Assets.xcassets等資源文件
    • PBXShellScriptBuildPhase : 對(duì)應(yīng)Xcode中Build Phases下的腳本文件,包括:Embed Pods Frameworks,Check Pods Manifest.lock以及其他本地或者第三方的腳本文件信息
    • PBXSourcesBuildPhase: 對(duì)應(yīng)Xcode中Build Phases的Complie Sources的代碼文件
  • PBXContainerItemProxy: 這里記錄了每個(gè)target的targetProxy,與PBXTargetDependency相對(duì)應(yīng)
  • PBXFileElement
    • PBXFileReference: 記錄了每個(gè)代碼文件的文件類型、路徑path、sourceTree, 不論引入文件的時(shí)候是create group還是create reference,都會(huì)在這里添加一條記錄
    • PBXGroup:工程中所有文件的group信息,這個(gè)和xcode文件目錄是對(duì)應(yīng)的,每一層的文件目錄有唯一的UUID,同一層group下的子group會(huì)和上一層的group的UUID有很高的重合度(基本只有1-2位不同),這個(gè)PBXGroup section中,子group沒有用樹的方式,而是采用類似列表的方式呈現(xiàn)了所有的group目錄,可以腦補(bǔ):打開xcode左側(cè)目錄,然后讓所有目錄和文件"左對(duì)齊",然后就會(huì)生成如下的結(jié)構(gòu)`
    • PBXVariantGroup: 不同地區(qū)的資源文件的引用信息,如果你項(xiàng)目使用了國(guó)際化,相關(guān)的xxx.string就在這個(gè)section中
  • PBXTarget: 每個(gè)Target的BuildSettings和BuildPhases(Sources/Frameworks/Resources等)的信息
    • PBXAggregateTarget: TODO: 暫未找到相關(guān)介紹,自己的項(xiàng)目里也沒出現(xiàn)這類Target
    • PBXLegacyTarget:TODO: 暫未找到相關(guān)介紹,自己的項(xiàng)目里也沒出現(xiàn)這類Target
    • PBXNativeTarget: 正常建立的Target都是這種類型的
  • PBXProject:整個(gè)項(xiàng)目工程Project的信息,包括項(xiàng)目路徑、Config信息,相關(guān)版本號(hào),所有的Target等信息
  • PBXTargetDependency: 記錄了每個(gè)Target的targetProxy,每個(gè)targetProxy都是一個(gè)PBXContainerItemProxy類型,暫時(shí)沒找到Xcode中的對(duì)應(yīng)項(xiàng)
  • XCBuildConfiguration: 對(duì)應(yīng)Xcode中 Build Settings中的配置信息
  • XCConfigurationList: XCBuildConfiguration只是列舉了所有Target的所有Setting項(xiàng),下面這個(gè)文件區(qū)分,不同Target在Debug時(shí)使用哪個(gè)Setting項(xiàng),在Release時(shí)使用哪個(gè)Setting項(xiàng)

分享一點(diǎn)其他人的總結(jié):

  • PBXProject 為根節(jié)點(diǎn),代表著整個(gè)工程

  • PBXProject 可以有多個(gè)PBXNativeTarget,代表著工程中的target

  • PBXNativeTarget 維護(hù)著各自資源文件(PBXResourcesBuildPhase),源文件(PBXSourcesBuildPhase),以及依賴庫(kù)(PBXFrameworksBuildPhase)等等

  • PBXProject 和 PBXNativeTarget 都有配置管理,通過XCConfigurationList和XCBuildConfiguration維護(hù)

  • 每個(gè)導(dǎo)入工程的文件都會(huì)有相應(yīng)的PBXFileReference記錄,如果該文件在導(dǎo)入時(shí),選擇了create groups ,會(huì)在相應(yīng)的PBXGroup中有記錄

  • 每個(gè)在編譯打包過程中被包含到可執(zhí)行文件中的文件,都會(huì)有PBXBuildFile記錄,根據(jù)類別分別在PBXResourcesBuildPhase,PBXSourcesBuildPhase等中有記錄

    --- From ehyubewb, 2018

2.3.4 Reference Hierarchy

XcodeProj本身所有的引用是基于每個(gè)對(duì)象的UUID的, pbxplorer 這個(gè)庫(kù)實(shí)現(xiàn)了對(duì)xcodeproj的解析,他在實(shí)現(xiàn)過程中,Reference Hierarchy如下:

PBXProject
    build_configuration_list: XCConfigurationList
    main_group: PBXGroup
    targets: [PBXNativeTarget]
  
XCConfigurationList
    build_configurations: [XCBuildConfiguration]
  
PBXGroup
    children: [PBXGroup|PBXFileReference]
    subgroups: [PBXGroup]
    file_refs: [PBXFileReference]
    variant_groups: [PBXVariantGroup]

PBXNativeTarget
    build_configuration_list: XCBuildConfigurationList
    build_phases: [PBXBuildPhase]
    product_file_ref: PBXFileReference

PBXBuildPhase
    build_files: [PBXBuildFile]

PBXBuildFile
    file_ref: PBXFileReference

如果想自己開發(fā)一套XcodeProj的框架或者處理腳本,可以參照這個(gè)Reference Hierarchy,對(duì)于類之間的彼此關(guān)聯(lián)會(huì)更加清楚。

3. 總結(jié)

XcodeProj大體來說就是配置了項(xiàng)目的文件路徑信息PBXBuildFile、項(xiàng)目中的Target及其依賴信息、編譯中的Config信息(PBXBuildPhase、XCBuildConfiguration等)。大致了解了他的結(jié)構(gòu)后,就會(huì)覺得雖然各方面井然有序,基于UUID實(shí)現(xiàn)關(guān)聯(lián),但整體還是顯得過于龐大。尤其當(dāng)項(xiàng)目越來越大的時(shí)候,XcodeProj打開就是一場(chǎng)噩夢(mèng)。

對(duì)于我個(gè)人而言,我更喜歡簡(jiǎn)單輕量級(jí)的IDE模式,類似Sublime/VSCode。假設(shè)作以下改變:

對(duì)于其中的文件信息,如果Xcode不考慮支持各種Group模式,完全物理實(shí)體目錄一一對(duì)應(yīng)的話,那就只剩下一些Target和依賴庫(kù)信息和相關(guān)的Config信息了。那這些信息本質(zhì)上就是一些Config信息。那這些Config再按照Build、Info、Res等分類為不同的Config,每個(gè)Config用json實(shí)現(xiàn)具體的內(nèi)容。

那么這樣一簇Config信息+源代碼文件組成的一個(gè)Project,就可以不束縛于唯一的IDE了,可以在一些常用的IDE中快速實(shí)現(xiàn)開發(fā)功能。

當(dāng)然要考慮Debug,得再加上Clang編譯器的能力,加上快捷提示的功能,加上...

那進(jìn)一步想一想,如果我們自己寫一個(gè)IDE, 我們需要做哪些準(zhǔn)備呢?


參考資料

  1. GitHub: CocoaPods 官方源碼
  2. GitHub: mjmsmith/pbxplorer
  3. Xcode Project File Format: 對(duì).pbxproj文件每個(gè)參數(shù)的詳細(xì)介紹
  4. XCode工程文件結(jié)構(gòu)及Xcodeproj框架的使用( 二 )
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 白思璇閱讀 215評(píng)論 0 0
  • 在鏡前的自己,因?yàn)樽约喝跣?,才有理埋怨朋友;因?yàn)樽约喝跣?,才勇敢說出傷害朋友的話;因?yàn)樽约喝跣。車磺卸汲蔀榱藬?..
    小丿年閱讀 217評(píng)論 0 0
  • 有人說這是腦洞題,也就是應(yīng)用題,也有人分析這是偵探題,是醫(yī)學(xué)題。 姜思達(dá)有個(gè)點(diǎn)是很好的,記憶的重量是人生必要的承重...
    六月花閱讀 480評(píng)論 0 0

友情鏈接更多精彩內(nèi)容