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

1. 背景

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

所以決定研究下,這個(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之類(lèi)的江耀,不重要剩胁,先忽略。我們主要來(lái)看pbxproj文件祥国。

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

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

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è)代碼文件的文件類(lèi)型何恶、路徑path孽锥、sourceTree,不論引入文件的時(shí)候是create group還是create reference,都會(huì)在這里添加一條記錄 */
        /* Begin PBXFileReference section */
        /* End PBXFileReference section */

        /* 工程中所依賴(lài)的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沒(méi)有用樹(shù)的方式,而是采用類(lèi)似列表的方式呈現(xiàn)了所有的group目錄抵卫,可以腦補(bǔ):打開(kāi)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類(lèi)型,暫時(shí)沒(méi)找到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è)類(lèi)和及其屬性字段的含義和引用關(guān)系。

從Plist的角度看碍舍,我們可以將PBXProject看成一個(gè)個(gè)節(jié)點(diǎn)和子節(jié)點(diǎn)的樹(shù)形結(jié)構(gòu)督弓,但從面向?qū)ο蟮慕嵌龋鋵?shí)就是一個(gè)個(gè)類(lèi)和子類(lèi)乒验。

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

xcodeproject_2018-08-06_23.png

2.3.2 Class Hierarchy

下面具體說(shuō)一下每個(gè)節(jié)點(diǎn)/類(lèi) 模塊包含的內(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: 工程中所依賴(lài)的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è)代碼文件的文件類(lèi)型妈踊、路徑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沒(méi)有用樹(shù)的方式呐伞,而是采用類(lèi)似列表的方式呈現(xiàn)了所有的group目錄敌卓,可以腦補(bǔ):打開(kāi)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)目里也沒(méi)出現(xiàn)這類(lèi)Target
    • PBXLegacyTarget:TODO: 暫未找到相關(guān)介紹蜗巧,自己的項(xiàng)目里也沒(méi)出現(xiàn)這類(lèi)Target
    • PBXNativeTarget: 正常建立的Target都是這種類(lèi)型的
  • PBXProject:整個(gè)項(xiàng)目工程Project的信息,包括項(xiàng)目路徑蕾盯、Config信息幕屹,相關(guān)版本號(hào),所有的Target等信息
  • PBXTargetDependency: 記錄了每個(gè)Target的targetProxy刑枝,每個(gè)targetProxy都是一個(gè)PBXContainerItemProxy類(lèi)型香嗓,暫時(shí)沒(méi)找到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),以及依賴(lài)庫(kù)(PBXFrameworksBuildPhase)等等

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

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

  • 每個(gè)在編譯打包過(guò)程中被包含到可執(zhí)行文件中的文件侈贷,都會(huì)有PBXBuildFile記錄,根據(jù)類(lèi)別分別在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)過(guò)程中上遥,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

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

3. 總結(jié)

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

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

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

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

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

那進(jìn)一步想一想嗜憔,如果我們自己寫(xiě)一個(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)系作者
  • 序言:七十年代末氏仗,一起剝皮案震驚了整個(gè)濱河市吉捶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌皆尔,老刑警劉巖帚稠,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異床佳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)榄审,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)砌们,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事浪感∥敉罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵影兽,是天一觀(guān)的道長(zhǎng)揭斧。 經(jīng)常有香客問(wèn)我,道長(zhǎng)峻堰,這世上最難降的妖魔是什么讹开? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮捐名,結(jié)果婚禮上旦万,老公的妹妹穿的比我還像新娘。我一直安慰自己镶蹋,他們只是感情好成艘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著贺归,像睡著了一般淆两。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拂酣,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天秋冰,我揣著相機(jī)與錄音,去河邊找鬼踱葛。 笑死丹莲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尸诽。 我是一名探鬼主播甥材,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼性含!你這毒婦竟也來(lái)了洲赵?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤商蕴,失蹤者是張志新(化名)和其女友劉穎叠萍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绪商,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苛谷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了格郁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腹殿。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡独悴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锣尉,到底是詐尸還是另有隱情刻炒,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布自沧,位于F島的核電站坟奥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拇厢。R本人自食惡果不足惜爱谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旺嬉。 院中可真熱鬧管行,春花似錦、人聲如沸邪媳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雨效。三九已至迅涮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間徽龟,已是汗流浹背叮姑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留据悔,地道東北人传透。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像极颓,于是被迫代替她去往敵國(guó)和親朱盐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 白思璇閱讀 122評(píng)論 0 0
  • 在鏡前的自己菠隆,因?yàn)樽约喝跣”眨庞欣砺裨古笥眩灰驗(yàn)樽约喝跣『Ь叮庞赂艺f(shuō)出傷害朋友的話(huà)躯肌;因?yàn)樽约喝跣。車(chē)磺卸汲蔀榱藬?..
    小丿年閱讀 180評(píng)論 0 0
  • 有人說(shuō)這是腦洞題,也就是應(yīng)用題晰筛,也有人分析這是偵探題嫡丙,是醫(yī)學(xué)題忠售。 姜思達(dá)有個(gè)點(diǎn)是很好的,記憶的重量是人生必要的承重...
    六月花閱讀 440評(píng)論 0 0